HDU 1565 方格取数(1)<<状压dp
题意
给定n*n的一个矩阵,每个格子有个非负数,要求是取了这个数就不能取它相邻的数,要使得取得数总和最大。
思路
这题一开始完全不知道怎么搞,查了题解之后发现有状压的做法和网络流(最大团)的做法,然而最大团已经触及我的知识盲区了,有空再补一下这个做法。
这里谈状压dp的解法。限于空间和时间,当然不能把整个棋盘都压缩成一个状态,仔细观察后发现,一行能怎么取数只和当前行以及相邻行相关,于是,我们只要按行枚举,把一行的状态压缩,判断与相邻行的关系来确定是否可行,然后状态转移。在此预处理出一行的可能状态数,发现n=20时可能的状态数仅有20000种不到。
由此,设dp[i][j]代表第i行状态为j时的最大取值,状态转移方程为$dp[i][k]=max(dp[i][k],dp[i-1][j])$其中j与k两状态相容,怎么判断相容呢?显然只要保证j与k每一位都不同即可。
最简单的表示法是$(state[j]\&state[k])==0$此处要注意优先级,另一种写法是$(state[j]|state[k])==state[j]+state[k]$
这题卡了空间,按这样开开不下一行所有的状态数,此处有两种解决方案,最好的解决方案我认为就是离散化状态,只保留可行状态(20000种),另一种就是滚动数组啦,因为状态转移时只要考虑当前行和上一行。当然也可以两种都用,将空间节省到极致,但是你两个数组在倒空间的时候会浪费时间,所以我觉得此处没有必要这么写,这样写还增加了码量……。
还有一个细节,就是根据枚举的状态算当前行的值的时候,如果在线算,就相当于多了一个n的复杂度,所以大概会慢十倍左右,我第一份代码就是在线算的。仔细一想,这部分可以完全事先处理好。
代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 int arr[25][25]; 4 vector<int> all; 5 int n; 6 int dp[22][1<<15]; 7 bool check(int state) 8 { 9 while(state) 10 { 11 if(state&1&&state&2) return false; 12 state>>=1; 13 } 14 return true; 15 } 16 void db() 17 { 18 all.clear(); 19 for(int i=0;i<(1<<n);i++) 20 { 21 if(check(i)) 22 all.push_back(i); 23 } 24 } 25 int cal(int r,int state) 26 { 27 int ret=0; 28 for(int i=n-1;i>=0;i--) 29 { 30 if(state&1) 31 ret+=arr[r][i]; 32 state>>=1; 33 } 34 return ret; 35 } 36 int main() 37 { 38 while(~scanf("%d",&n)) 39 { 40 db(); 41 for(int i=0;i<n;i++) 42 for(int j=0;j<n;j++) 43 scanf("%d",&arr[i][j]); 44 memset(dp,0,sizeof(dp)); 45 int ans=0; 46 for(int i=0;i<n;i++) 47 { 48 for(int j=0;j<all.size();j++) 49 { 50 if(i==0) 51 { 52 dp[i][j]=cal(i,all[j]); 53 ans=max(ans,dp[i][j]); 54 //cout<<bitset<3>(all[j])<<","<<dp[i][all[j]]<<endl; 55 } 56 else{ 57 for(int k=0;k<all.size();k++) 58 { 59 if(!(all[j]&all[k])) 60 { 61 //cout<<bitset<5>(all[j])<<","<<bitset<5>(all[k])<<endl; 62 dp[i][k]=max(dp[i-1][j]+cal(i,all[k]),dp[i][k]); 63 ans=max(ans,dp[i][k]); 64 } 65 } 66 } 67 } 68 } 69 printf("%d\n",ans); 70 } 71 }
#include<bits/stdc++.h> using namespace std; int arr[25][25]; vector<int> all;//存储所有可能的状态 int n; int val[25][1<<15];//预处理每行在各个状态的和 int dp[22][1<<15]; bool check(int state) { while(state) { if(state&1&&state&2) return false; state>>=1; } return true; } int cal(int r,int state)//计算各行和 { int ret=0; for(int i=n-1;i>=0;i--) { if(state&1) ret+=arr[r][i]; state>>=1; } return ret; } void db()//预处理出状态和各行和 { all.clear(); int cnt=0; for(int i=0;i<(1<<n);i++) { if(check(i)) { for(int j=0;j<n;j++) { val[j][cnt]=cal(j,i); } all.push_back(i); cnt++; } } } int main() { while(~scanf("%d",&n)) { for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&arr[i][j]); db(); memset(dp,0,sizeof(dp)); int ans=0; for(int i=0;i<n;i++)//当前行 { for(int j=0;j<all.size();j++)//枚举上一行状态 { if(i==0) { dp[i][j]=val[i][j]; ans=max(ans,dp[i][j]); //cout<<bitset<3>(all[j])<<","<<dp[i][all[j]]<<endl; } else{ for(int k=0;k<all.size();k++)//枚举当前行状态 { if(!(all[j]&all[k])) { //cout<<bitset<5>(all[j])<<","<<bitset<5>(all[k])<<endl; dp[i][k]=max(dp[i-1][j]+val[i][k],dp[i][k]); ans=max(ans,dp[i][k]); } } } } } printf("%d\n",ans); } }