题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6289
题意:
给你n*m的格子,每个格子上有一个值,现在你在(1,1),你只能往下和往右走一直到(n,m),并获得经过的路上的格子的值,你现在有k次机会可以交换两个不同格子上的权值,问你最后能获得的最大值为多少。
做法:
dp,真的不是很好想啊....就算知道是dp,知道每一维代表什么也不是一下子就能写出来的...思维dp,四位分别代表,走到位置(i,j)时,捡了p个物品和使用了q个物品的最大值。
这个捡了p个和使用了q个一下子有点让人困惑,其实就是你要保持住这个状态,便于更新,后两维有点像是背包的感觉,看完大佬的博客后自己的理解是。在往右走的时候,是不做很大改动的,只是保存住当前这个状态而已,而当你往下走的时候,你要对下一行左边你没有走过的点和当前行往后你也走不了了的点做背包的更新,先保存下来这些值并做好预处理,然后当要作转移的时候,要枚举还需要使用多少次机会(显然已经用了的和你还要用的是不能超过k次的),再用这k次机会去使用你之前保存下来的前k个最大的值,然后不断的作更新,最后你只要枚举在(n,m)这个点,使用次数和捡物品个数相同的dp,找一个最大值即可。
代码或多或少有点雷同吧,毕竟是参考着写的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int mp[55][55],dp[55][55][25][25],tmp[105],now,n,m,k;
bool cmp(int a,int b){return a>b;}
void fresh(int &a,int b){if(a<b) a=b;}
int main(){
int t;
cin>>t;
while(t--){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&mp[i][j]);
memset(dp,-1,sizeof(dp));
dp[1][1][0][0]=mp[1][1];
dp[1][1][1][0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
tmp[0]=now=0;
for(int z=1;z<=m;z++){
if(z<j) tmp[++now]=mp[i+1][z];
else if(z>j) tmp[++now]=mp[i][z];
}
sort(tmp+1,tmp+1+now,cmp);
for(int i=1;i<=now;i++)
tmp[i]+=tmp[i-1];
for(int p=0;p<=k;p++){//已经拿了p个,和已经用了q个的最大值
for(int q=0;q<=k;q++){
if(dp[i][j][p][q]==-1) continue;
int now=dp[i][j][p][q];
if(j<m){
fresh(dp[i][j+1][p][q],now+mp[i][j+1]);
if(p<k) fresh(dp[i][j+1][p+1][q],now);
}
if(i<n){
for(int z=0;z<=k;z++){
if(z+q<=k){
fresh(dp[i+1][j][p][z+q],now+tmp[z]+mp[i+1][j]);
if(p<k) fresh(dp[i+1][j][p+1][z+q],now+tmp[z]);
}
}
}
}
}
}
}
int ans=dp[n][m][0][0];
for(int i=1;i<=k;i++)
ans=max(ans,dp[n][m][i][i]);
printf("%d\n",ans);
}
return 0;
}