代码比较简单的一题,重在思路(除非写假了)
传送门https://www.luogu.com.cn/problem/P1004
我的最初思路是两次二维dp,即贪心的取,用pre记录前一个位置,只有80pts,要是是在蓝桥拿分就可以跑路了(bushi)
代码如下
// Problem:
// P1004 [NOIP2000 提高组] 方格取数
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1004
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)#include<iostream>
#include<cstring>
using namespace std;
const int N=10;
int ans=0;
int f[N][N];
int pre[N][N];//记录前一个位置
int val[N][N];int main(){int n;cin>>n;int a,b,c;while(cin>>a>>b>>c){if(a==0&&b==0&&c==0){break;}val[a][b]=c;}for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){f[i][j]=max(f[i-1][j],f[i][j-1])+val[i][j];if(f[i-1][j]>=f[i][j-1]){pre[i][j]=1;//1从上一行来}else{pre[i][j]=2;//2从左边来}}}for(int i=1;i<=n;++i) pre[1][i]=2;for(int i=1;i<=n;++i) pre[i][1]=1;ans+=f[n][n];memset(f,0,sizeof f);int line=n;int rank=n;// for(int i=1;i<=n;++i){// for(int j=1;j<=n;++j){// cout<<val[i][j]<<' ';// }// cout<<endl;// }// cout<<endl;// for(int i=1;i<=n;++i){// for(int j=1;j<=n;++j){// cout<<pre[i][j]<<' ';// }// cout<<endl;// }val[rank][line]=0;while(rank>1||line>1){if(pre[rank][line]==1){rank-=1;}else{line-=1;}val[rank][line]=0;}for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){f[i][j]=max(f[i-1][j],f[i][j-1])+val[i][j];}}// cout<<ans<<endl;ans+=f[n][n];cout<<ans<<endl;return 0;
}
这样子代码的问题在于,这道题两次要同时最优,并不能直接贪心
(或许是可以做的,但是要考虑的复杂情况比较多,本蒟蒻决定跳过)
7
1 3 2
1 4 3
2 3 3
3 3 3
5 5 4
6 5 4
7 3 2
7 5 4
0 0 0
例如这组数据,用上面的代码跑一下就会发现,不使用贪心(每次都取最大)的方法能获得更多的价值,于是,我们可以考虑使用一个四维dp来同时维护两次操作
// Problem:
// P1004 [NOIP2000 提高组] 方格取数
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1004
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)#include<iostream>
#include<cstring>
using namespace std;
const int N=10;
int ans=0;
int f[N][N][N][N];
int val[N][N];int main(){int n;cin>>n;int a,b,c;while(cin>>a>>b>>c){if(a==0&&b==0&&c==0){break;}val[a][b]=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]))+val[i][j]+val[k][l];if(i==k&&j==l) f[i][j][k][l]-=val[i][j];}}}}cout<<f[n][n][n][n]<<endl;return 0;
}
这里的四维也比较好理解,其实就是枚举了两种路线分别经过每一个点的状态,并且放在一起考虑(相等要减掉val[i][j])
另外:写二维的时候还没有明显的感觉,后面发现这不就是深搜变种嘛,洛谷有一题很像
本题数据是N<=9
但是我们有更好的三维优化 三维思路出处
思路:用k来表示x坐标和y坐标的和,然后通过y算出x坐标,创造一个f[k][y1][y2]来跑dp
和前面思路大致相同,有转移公式(思路,不是原代码)
f[k][i][j]=max(f[k−1][i][j],f[k−1][i−1][j],f[k−1][i][j−1],f[k−1][i−1][j−1])
+[(i==j)?map[k−i+1][i]:map[k−i+1][i]+map[k−j+1][j]]
其实这个方程不难理解,可以类似于用bfs的扩散来协助理解。枚举的k实际上就是步数,i,j就是枚举该不输的每个点。在这个dp公式中,如果i不动,k-1,那么其实就是行数-1,否则就是列数-1,巧妙的表现了两种情况。
感觉是很熟悉的优化,但是我想不到
代码如下:
// Problem:
// P1004 [NOIP2000 提高组] 方格取数
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1004
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)#include<iostream>
#include<cstring>
using namespace std;
const int N=20;//数组最好开大一点,不然会越界(但是洛谷能过就是了)
int ans=0;
int f[N][N][N];
int val[N][N];int main(){int n;cin>>n;int a,b,c;while(cin>>a>>b>>c){if(a==0&&b==0&&c==0){break;}val[a][b]=c;}for(int k=2;k<=n*2;++k){//从2开始,不能从3,因为要考虑(1,1)的情况for(int i=1;i<=min(k-1,n);++i){//k-1 是因为要考虑横坐标至少有1for(int j=1;j<=min(k-1,n);++j){f[k][i][j]=max(max(f[k-1][i-1][j-1],f[k-1][i][j-1]),max(f[k-1][i-1][j],f[k-1][i][j]))+val[k-j][j]+val[k-i][i];if(i==j) f[k][i][j]-=val[k-i][i];//重复就减掉}}}cout<<f[n*2][n][n]<<endl;return 0;
}