Eg 1 方格取数 (NOIP 2000) 如果用两次一般的动归,可能不能得到最优解。这如同一个大苹果加一个小苹果还不如两个中苹果加在一起。这时就要考虑更改状态。因为单独看第一个人的位置或第二个人的位置并不能组成一个完整的状态,但两个人的位置合起来就是一个完整的状态了,所以新的状态包含了两个人的位置。 新的状态为f[x1][y1][x2][y2],表示两个人分别走到(x1,y1)和(x2,y2)时取得的最大值。将它作为状态,决策将会有4种: 1.(第一个人从上面走来,第二个人从上面走来) 2.(第一个人从上面走来,第二个人从左面走来) 3.(第一个人从左面走来,第二个人从上面走来) 4.(第一个人从左面走来,第二个人从左面走来) 除了考虑决策,还要考虑同行、同列和同点的情况。
关键DP代码
for(int x1=1; x1<=n; x1++) { for(int y1=1; y1<=n; y1++) { for(int x2=1; x2<=n; x2++) { for(int y2=1; y2<=n; y2++) { int temp=0x80000000; temp=std::max(temp, f[x1-1][y1][x2-1][y2]); temp=std::max(temp, f[x1-1][y1][x2][y2-1]); temp=std::max(temp, f[x1][y1-1][x2-1][y2]); temp=std::max(temp, f[x1][y1-1][x2][y2-1]); if(x1==x2 && y1==y2) //同一点只加一次分 { f[x1][y1][x2][y2]=temp+rect[x1][y1]; } else { f[x1][y1][x2][y2]=temp+rect[x1][y1]+rect[x2][y2] } } } } } //return dp[n][n][n][n]+rect[1][1]; //记住加上第一个数状态的优化 可以再一次改变状态的表示法,减少状态的维数。 如果我们定义f[steps][x1][x2]为每个人的横坐标和纵坐标之和为steps,第一个人的横坐标为x1,第二个人的横坐标为x2,这也是可以唯一而正确地表示状态的。
const int maxn=15; int n; int rect[maxn][maxn]; int f[maxn*2][maxn][maxn]; int DP() { int steps=2*n; //最终坐标之和为2*n for(int i=3; i<=steps; i++) //坐标一开始为(1,1),所以从2+1开始算起 { for(int x1=1; x1<=std::min(n,i-1); x1++) //坐标最大为 行坐标 和 坐标和-1 之间的最小值 { for(int x2=1; x2<=std::min(n,i-1); x2++) { int temp=0x80000000; temp=std::max(temp,f[i-1][x1][x2]); temp=std::max(temp,f[i-1][x1-1][x2]); temp=std::max(temp,f[i-1][x1][x2-1]); temp=std::max(temp,f[i-1][x1-1][x2-1]); if(x1==x2) f[i][x1][x2]=temp+rect[x1][i-x1]; else f[i][x1][x2]=temp+rect[x1][i-x1]+rect[x2][i-x2]; } } } return f[steps][n][n]+rect[1][1]; //一定要记住加上rect[1][1] }Eg 2 传纸条(NOIP 2008) 与上一题不同的是,这一题不能走过同一个点,因此限制条件不同,但大体思路基本一样:
int DP() { for(int x1=1; x1<=r; x1++) { for(int y1=1; y1<=c; y1++) { for(int x2=1; x2<=r; x2++) { for(int y2=1; y2<=c; y2++) { if(x1>=x2 && y1>=y2 && (x1<r || y1<c)) continue; //除了终点,否则不能过同一个点 //(x1,y1)和(x2,y2)互换时结果相同,可以不计算 int temp=0; temp=std::max(temp,f[x1-1][y1][x2-1][y2]); if(x1-1 != x2 && y1 != y2-1) //源点必须不同 temp=std::max(temp,f[x1-1][y1][x2][y2-1]); if(x1 != x2-1 && y1-1 != y2) temp=std::max(temp,f[x1][y1-1][x2-1][y2]); temp=std::max(temp,f[x1][y1-1][x2][y2-1]); f[x1][y1][x2][y2]=temp+rect[x1][y1]+rect[x2][y2]; } } } } return f[r][c][r][c]; }状态优化 同理,我们也可以将状态定义为f[steps][x1][x2],含义跟Eg 1中的一样。
const int maxn=55; int r,c; int rect[maxn][maxn]; int f[2*maxn][maxn][maxn]; int DP() { int steps=r+c; //坐标和最终为r+c for(int i=3; i<=steps; i++) { for(int x1=1; x1<=std::min(r,i-1); x1++) //行坐标最大为 行数 和 坐标和-1 中的最小值 { for(int x2=1; x2<=std::min(r,i-1); x2++) { if(x1==x2 && i!=steps) continue; //除了终点,其它点的横坐标不能相等 int temp=0; temp=std::max(temp, f[i-1][x1][x2]); if(x1-1!=x2) //源点不能相同 temp=std::max(temp, f[i-1][x1-1][x2]); if(x1!=x2-1) temp=std::max(temp, f[i-1][x1][x2-1]); temp=std::max(temp, f[i-1][x1-1][x2-1]); f[i][x1][x2]=temp+rect[x1][i-x1]+rect[x2][i-x2]; } } } return f[steps][r][r]; //和为steps,行坐标分别为r,r }