状态压缩dp
状态压缩DP 20150715
1、前言
动态规划,永远的痛。
好了不扯远了。状态压缩动态规划,其实看名字还是较好理解的。我们在动态规划的时候,最重要的就在于状态的设计和状态转移方程。那么,如果当我们状态过多导致时间或空间不够的饿时候,就可以用到状态压缩。王队(@wyh2000)说状态压缩DP难起来的话会很难,但是今天我们只讲最最最基础的状态压缩方式,以后再慢慢补充。
2、区别
①有一个1*n的棋盘(n<=80),需要在上面放置k个棋子,使棋子之间不相邻,求方案数。
那么这是一道很简单的棋盘型DP。设f[i][j][0]为前i个放置j个棋子的方案数且第i位必放,f[i][j][1]为前i个放置j个棋子的方案数且第i位必不放。则存在方程:
②有一个m*n的棋盘(n*m<=80),需要在上面放置k个棋子,使棋子之间不相邻,求方案数。
多了一个行的状态,这就让人费解了——我们并不能设置一个四维方程来表示状态。原来我们每一行只有一个格子,现在多个格子怎么表示呢?这里就要用到状态压缩了。这里提到的状态压缩的方式只是其中一种,相对比较简单的一种——二进制转换。我们注意到题目有一个特别鬼畜的数据范围——n*m<=80。这意味着什么?9*9=81>80,则min(n,m)的最大值为8。我们将m,n中较小的一个看做行(易得行列转换依旧等价),一行至多8个格子,我们令当前状态下格子若放置了棋子则记为1,未放置记为0,那么我们可以将一行的状态表示为一个二进制数,继而在状态转移的时候再转换为十进制。前文提到了最多8个数,所以数组中数值最大为2^8=256。设f[i][j][k]表示当前第i列,使用了j个棋子,当前行的状态为k(一个由二进制数转换过来的十进制数),则状态转移方程为:
当然我们要注意——如何判断当前这一行与上一行是否存在相邻的棋子?利用按位异或即可。见代码。
------------------------------------------------------------------------------------------------------------------
#include cstdio #include cstring #include algorithm using namespace std; long long f[81][1<<9][21]; inline int getNum(int x) { int s=0,tmp=0; while (x) { if (s && (x & 1)) return -1; if (s=(x & 1)) tmp++; x=x>>1; } return tmp; } int main() { int n,m,t; while (scanf("%d %d %d",&n,&m,&t)!=EOF) { if (n《m) swap(n,m); memset(f,0,sizeof(f)); f[0][0][0]=1; for (int i=1;i<=n;i++) for (int r=0;r<=t;r++) for (int j=0;j<(1<<m);j++) // 当前的状态 { int num=getNum(j); // 棋子个数 if (num==-1 || num>r) continue; for (int k=0;k<(1<<m);k++) // 上次的状态转移 { if (getNum(k)==-1 || k&j) continue; f[i][j][r]+=f[i-1][k][r-num]; } } long long ans=0; for (int i=0;i<(1<<m);i++) ans+=f[n][i][t]; printf("%lld\n",ans); } return 0; }
------------------------------------------------------------------------------------------------------------------
3、例题
代码:
-----------------------------------------------------------------------------------------------------------------
#include cstdio #include cstring #define MAXN 11 #define INF 0x3f3f3f3f int min(int a,int b) { return (a《b)?a:b }; const int two[MAXN]={1,2,4,8,16,32,64,128,256,512}; int map[MAXN][MAXN],f[MAXN][MAXN][(1<<10)+1],ans,ret,tot; int getNum(int ki,int now) { int temp=ki; for (int i=9;i>=0;i--) if (temp>=two[i]) { temp-=two[i]; if (i==now) return ki; } return (ki+two[now]); } int main() { memset(f,INF,sizeof(f)); for (int i=1;i<=10;i++) for (int j=1;j<=10;j++) scanf("%d",&map[i][j]); int t1=two[map[1][1]],t2=0; f[1][1][t1]=map[1][1]; for (int i=2;i<=10;i++) { t2=getNum(t1,map[1][i]); f[1][i][t2]=f[1][i-1][t1]+map[1][i]; t1=t2; } t1=two[map[1][1]]; for (int i=2;i<=10;i++) { t2=getNum(t1,map[i][1]); f[i][1][t2]=f[i-1][1][t1]+map[i][1]; t1=t2; } for (int i=2;i<=10;i++) for (int j=2;j<=10;j++) { for (int ki=1;ki<=(1<<10)-1;ki++) { if (f[i-1][j][ki]==INF) continue; int k=getNum(ki,map[i][j]); f[i][j][k]=min(f[i][j][k],f[i-1][j][ki]+map[i][j]); } for (int ki=1;ki<=(1<<10)-1;ki++) { if (f[i][j-1][ki]==INF) continue; int k=getNum(ki,map[i][j]); f[i][j][k]=min(f[i][j][k],f[i][j-1][ki]+map[i][j]); } } for (int i=1;i<=1023;i++) if (f[3][3][i]!=INF) printf("i=%d %d\n",i,f[3][3][i]); printf("%d",f[10][10][1023]); return 0; } -----------------------------------------------------------------------------------------------------------------