POJ-1038 Bugs Integrated, Inc. (状压+滚动数组+深搜 的动态规划)
本题的题眼很明显,N (1 <= N <= 150), M (1 <= M <= 10),摆明了是想让你用状态压缩dp。
整个思路如下:由于要填2*3或者3*2的芯片,那么就要找一个策略来判断到底能不能填。
精华1在此:
找到的策略是,记格子(i,j)的状态有三种:
状态0代表(i,j)和(i-1,j)均可用(可用包括非损坏和未占用)
状态1代表(i,j)可用但(i-1,j)不可用
状态2代表(i,j)和(i-1,j)均不可用。
这样设置状态后,我们可以将填芯片这个问题策略化描述:
即能够填芯片的情况无非两种:
其一横着填3*2的芯片,此时只需满足state(i,j)=0&&state(i,j+1)=0&&state(i,j+2)=0即可(在代码中用的是pre和now数组存放的两行状态)
其二是竖着填2*3的芯片,此时只需满足state(i,j)=0&&state(i,j+1)=0&&state(i-1,j)=0&&state(i-1,j+1)=0即可。
这样一来就可以从第一行开始深搜每一行可能出现的填芯片的情况并计数。
同时由于每个格子的状态只有3种,因此可以用3进制数对每一行的状态进行记录,也就是所谓的状态压缩。
比如如果某一行的状态为(1,1,0,2,2),转化为3进制数就是1*3^0+1*3^1+0*3^2+2*3^3+2*3^4。这样就将一行的状态用一个3进制数表示了。
精华2:在考虑完如何进行状态压缩后下一步就是如何用dp来求解了。
思路如下:既然可以将一行的状态记录为一个3进制数 t,那么递推关系不妨按行递推,也就是考虑从第一行开始,每增加一行,能填的芯片数目会增加多少。
而由精华1不难发现,只要知道新增行和上一行的状态,就可以推出填芯片的所有可能方案(这里显然需要搜索解决)。
因此考虑这样进行存储:设置一个dp[i][j]数组,其中i表示第几行,j表示该行当前的状态(也就是上文所说的压缩得到的3进制数)。
dp[i][j]的值表示以第i行为最后一行并且第i行状态为j的情况下所能填入的最大芯片数。
那么dp的目标就转化为求出所有dp[i][j]的值。
这里的特殊性在于,递推关系不再是一个简单的式子,而是通过深搜来更新的。
至此,思路就已经相当明确 了。
为了节约内存空间,在具体计算dp数组时用了滚动数组的方法(毕竟每次更新只需要保存两行状态就可以了)
一些小tips和犯过的错都记录在代码里了,以备温故知新。
AC代码如下:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int n,m; int map[160][15]; int dp[2][59060]; int now[15]; int pre[15]; int t[12]={0,1,3,9,27,81,243,729,2187,6561,19683,59049};//3进制权重 int getstate(int a[]){ int tem=0; for(int i=1;i<=m;i++){ tem+=a[i]*t[i]; } return tem; } int getstring(int tem,int*a){ for(int i=1;i<=m;i++){ a[i]=tem%3; tem=tem/3; } //注意一下,注释里面是一开始写的错误程序; //这样写的错误在于 不能完整的修改字符串,无法正确处理高位若干0的情况 // int i=0; // while(tem!=0){ // a[++i]=tem%3; // tem/=3; // } } void dfs(int i,int j,int prenum,int state){//深搜的典型框架 dp[i%2][state]=max(dp[i%2][state],prenum); //dp[i%2][state]=prenum;//写成这样是错的 ,道理很简单,每种状态是可能被重复搜索到的,而 //在重复搜索到的时候就必须选择最大的那种情况才满足题意 ,一个简单的例子就是当某一行更新后状态为(2,2,2,2,2,2) //则可能有两种情况出现 ,一种是3个2*3的芯片,一种是2个3*2的芯片。 int k; if(j>=m)return; if(j<m-1&&now[j]==0&&now[j+1]==0&&now[j+2]==0){ now[j]=now[j+1]=now[j+2]=2; k=getstate(now); dfs(i,j+3,prenum+1,k); now[j]=now[j+1]=now[j+2]=0; } if(pre[j]==0&&pre[j+1]==0&&now[j]==0&&now[j+1]==0){ now[j]=2;now[j+1]=2; k=getstate(now); dfs(i,j+2,prenum+1,k); now[j]=0;now[j+1]=0; } dfs(i,j+1,prenum,state); return; } int main(void){ int d; scanf("%d",&d); while(d--){ int k; scanf("%d%d%d",&n,&m,&k); memset(map,0,sizeof(map)); while(k--){ int a,b; scanf("%d%d",&a,&b); map[a][b]=1; } for(int i=0;i<=t[m+1];i++){//一开始写的i=1 dp[1][i]=-1; } for(int i=1;i<=m;i++){ pre[i]=map[1][i]+1; } int state=getstate(pre); dp[1][state]=0; for(int i=2;i<=n;i++){//这里写的时候思路不连贯 for(int j=0;j<=t[m+1];j++){ dp[i%2][j]=-1; } for(int j=0;j<=t[m+1];j++){ if(dp[(i+1)%2][j]==-1)continue; getstring(j,pre); for(int k=1;k<=m;k++){ if(map[i][k]==1)now[k]=2; else if(pre[k]==2)now[k]=1; else{ now[k]=0; } } state=getstate(now); dfs(i,1,dp[(i+1)%2][j],state); } } int ans=0; for(int i=0;i<=t[m+1];i++){ ans=max(dp[n%2][i],ans); } printf("%d\n",ans); } return 0; }