题目描述
设有 N×NN imes NN×N 的方格图 (N≤9)(N le 9)(N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 000。如下图所示(见样例):
A
0 0 0 0 0 0 0 0
0 0 13 0 0 6 0 0
0 0 0 0 7 0 0 0
0 0 0 14 0 0 0 0
0 21 0 0 0 4 0 0
0 0 15 0 0 0 0 0
0 14 0 0 0 0 0 0
0 0 0 0 0 0 0 0
B
某人从图的左上角的 AAA 点出发,可以向下行走,也可以向右走,直到到达右下角的 BBB 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 000)。
此人从 AAA 点到 BBB 点共走两次,试找出 222 条这样的路径,使得取得的数之和为最大。
输入格式
输入的第一行为一个整数 NNN(表示 N×NN imes NN×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 000 表示输入结束。
输出格式
只需输出一个整数,表示 222 条路径上取得的最大的和。
输入输出样例
8 2 3 13 2 6 6 3 5 7 4 4 14 5 2 21 5 6 4 6 3 15 7 2 14 0 0 0
67
说明/提示
NOIP 2000 提高组第四题
第一遍:自己做题时候的思路
当时的做法其实应该称不上是dp,应该说是贪心?每次选择最优的解,记录经过路径,第一次遍历结束之后把跑过的路径值全部清零,再跑第二次,就可以得到最优解(贪心一定了)。
(由于数据太弱,这样的做法可以骗到60分)
#include<bits/stdc++.h> using namespace std; int n; int a[10][10]; struct node{ int last,s,x,y; }f[20][20]; int ans=0; int main() { cin>>n; int p; while(cin>>p&&p!=0){ int y,z; cin>>y>>z; a[p][y]=z; } // cout<<" ****"<<endl; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { f[i][j].s =0; f[i][j].last =-1; } /* 4 2 2 3 2 3 4 3 1 2 4 2 1 */ // cout<<" ****"<<endl; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(f[i-1][j].s +a[i][j]>f[i][j-1].s +a[i][j]) { f[i][j].s =f[i-1][j].s +a[i][j]; f[i][j].last =a[i-1][j]; f[i][j].x =i-1; f[i][j].y =j; } else { f[i][j].s =f[i][j-1].s +a[i][j]; f[i][j].last =a[i][j-1]; f[i][j].x =i; f[i][j].y =j-1; } } f[1][1].last =-1; // cout<<f[1][1].last <<endl; // cout<<" ****"<<endl; ans=f[n][n].s ; node now=f[n][n]; int x=f[n][n].x ; int y=f[n][n].y ; // cout<<" ****"<<endl; while(now.last!=-1&&x!=0&&y!=0) { // cout<<x<<" "<<y<<endl; a[x][y]=0; now=f[x][y]; x=now.x ; y=now.y ; } // cout<<" ****5"<<endl; a[now.x ][now.y ]=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { f[i][j].s =0; f[i][j].last =-1; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(f[i-1][j].s +a[i][j]>f[i][j-1].s +a[i][j]) { f[i][j].s =f[i-1][j].s +a[i][j]; f[i][j].last =a[i-1][j]; f[i][j].x =i-1; f[i][j].y =j; } else { f[i][j].s =f[i][j-1].s +a[i][j]; f[i][j].last =a[i][j-1]; f[i][j].x =i; f[i][j].y =j-1; } } ans+=f[n][n].s ; cout<<ans<<endl; return 0; }
但是我们仔细想一想就会发现,如果我们只是贪心求解的话,是存在情况不满足这种贪心方式的,比如一开始,我们求出了最长路径,并且成功把最长路径清零了,但是下一次我们就会发现我们有可能把完整路径破坏掉了!就是说一开始我们本可以采取其他的路径走法,使得权值的所有点都可以被遍历到,但是我们人为破坏了这样的路径,最终自然得不到最大路径了。
第二遍:正解(n^4)
我们来考虑正解,其实我们发现,题目中总共存在四个状态量。我们完全可以把题目想成这样:有两个人同时走,从起点到达终点,问这两个人的所走路径和最大是多少,我们不妨先将所有状态都设置出来,如f[i][j][k][l]表示第一个人所在位置是(i,j),第二个人所在位置是(k,l),这样的话,f的意思表示为第一个人在(i,j)的位置,第二个人在(k,l)的位置时所经过的最大路径之和,那么f[n][n][n][n]就是我们最后要求的答案
#include<bits/stdc++.h> using namespace std; int n; struct node{ int a,b,c; }s[502]; int mapa[502][502]; int f[20][20][20][20]; int main() { int p=0; cin>>n; while(1) { p++; cin>>s[p].a >>s[p].b >>s[p].c ; if(s[p].a ==0&&s[p].b ==0&&s[p].c ==0) break; } for(int i=1;i<=p;i++) { mapa[s[i].a ][s[i].b ]=s[i].c ; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) for(int l=1;l<=n;l++){ f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+mapa[i][j]+mapa[k][l]; if(i==k&&j==l) f[i][j][k][l]-=mapa[i][j]; } cout<<f[n][n][n][n]<<endl; return 0; }
第三遍:优化一(n^3)
其实我们可以把这个状态优化成n^3的复杂度。我们发现我们其实i+j和k+l的值是相同的(因为每次两个人都是只走一步),所以我们完全可以用i+j-k来表示l,这样的话就避免了一层循环
#include<cstdio> #include<algorithm> using namespace std; struct point { int x,y,data; }p[100]; int n,m,map[11][11],f[11][11][11][11]; int main() { int i,j,k,l; scanf("%d",&n); while(1) { int a,b,c; scanf("%d%d%d",&a,&b,&c); if(!a&&!b&&!c) break; p[++m].x=a; p[m].y=b; p[m].data=c; } for(i=1;i<=m;i++) map[p[i].x][p[i].y]=p[i].data; for(i=1;i<=n;i++) for(j=1;j<=n;j++) for(k=1;k<=n;k++) { l=i+j-k; if(l<=0) break; f[i][j][k][l]=max(f[i-1][j][k-1][l],max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))); if(i==k&&j==l) f[i][j][k][l]+=map[i][j]; else f[i][j][k][l]+=map[i][j]+map[k][l]; } printf("%d ",f[n][n][n][n]); return 0; }
第三遍:优化二(状态压缩),其实和上一个差不多,我们进行状态压缩,f[l][i][ii]表示当前总共走了l步,第一个人在(i,l-i),第二个人在(ii,l-ii),和上面一样进行循环枚举状态就可以
-----------------end-------------------