poj1038 Bugs Integrated, Inc.[状压DP]
改变了我对铺砖问题的认识。首先根据之前的理解,要表示这一行当前的铺设情况,注意到由于竖着摆砖是3层的,所以要保存两行的状态,也就是对于$a[i][j]和a[i-1][j]$的铺设状态。
1.如何设计状态
无非4种情况。
- $s_{i,j}=s_{i-1,j}=0$,两格都没铺。可用$0$表示
- $s_{i,j}=0,s_{i-1,j}=1$,用$1$表示
- $s_{i,j}=1,s_{i-1,j}=0$,这种可以和下面那种情况合并,毕竟dp的时候只要$i$行是$1$,上面一行是啥就不用管了,用$2$表示
- $s_{i,j}=s_{i-1,j}=1$,见上
合并后是3种,于是可以硬扛三进制,设$f_i,S$为第$i$行状态$S$时的最多铺设,这样就可以推了。
2.如何dp
其实是可以枚举当前层、再枚举上一层、然后判断合法性后转移的,但是这个属于暴力枚举(就是你做炮兵阵地用的方法),有大量无效状态被枚举了。可以采用轮廓线DP减少枚举,但据说会T,没试过。
这里为了保证只从当前状态推向下一层的合法状态,采用DFS来DP,即在枚举上一层$S$后,计算好下一层初始状态(就是如果上一层有某位是$2$,下一层应当是$1$,以此类推),然后再这个初始状态基础上铺砖完成有效枚举,同时更新答案。
这种方法可以基本解决很多铺砖问题。亦可以改造成统计方案数等等。
时间复杂度?理论$O(n3^{2m})$,但实际第二个$O(3^n)$根本跑不满,可能只是常数级别的,所以具体多快是玄学。
Details
- 为什么非要三进制?如果用类插头DP的两个二进制数表示,不方便枚举?也许也可以写,没想过。
- 无效状态用-1表示,枚举时直接略过。
- 三进制拆分、位运算注意一下
- 滚动数组,并在枚举后顺手清为-1。
- RE*1:line42的now忘算了。。
总结反思:这种逐行转移的状压dp,从考虑当前行已推好的状态开始,考虑向下一行的转移,用dfs的思路去想
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #define dbg(x) cerr << #x << " = " << x <<endl 7 using namespace std; 8 typedef long long ll; 9 typedef double db; 10 typedef pair<int,int> pii; 11 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 12 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 13 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 14 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 15 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 16 template<typename T>inline T read(T&x){ 17 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 18 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 19 } 20 const int N=150+3,M=10+3; 21 const int bin[]={1,3,9,27,81,243,729,2187,6561,19683,59049}; 22 int bad[N][M],now[M],pre[M]; 23 int f[2][59050];//59050=3^10 24 int T,n,m,k,d,ans; 25 void dp(int j,int sum,int cur){ 26 MAX(f[d^1][cur],sum); 27 if(j>=m)return; 28 if(!now[j]&&!now[j+1]&&!pre[j]&&!pre[j+1])dp(j+2,sum+1,cur+2*(bin[j-1]+bin[j]));//竖 29 if(j<m-1&&!now[j]&&!now[j+1]&&!now[j+2])dp(j+3,sum+1,cur+2*(bin[j-1]+bin[j]+bin[j+1]));//横 30 dp(j+1,sum,cur); 31 } 32 33 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 34 read(T);while(T--){ 35 read(n),read(m),read(k); 36 memset(f,-1,sizeof f),memset(bad,0,sizeof bad),ans=0; 37 for(register int i=1,x,y;i<=k;++i)read(x),read(y),bad[x][y]=1; 38 f[d=0][bin[m]-1]=0; 39 for(register int i=1;i<=n;++i,d^=1){ 40 for(register int s=0,cur=0;s<bin[m];++s,cur=0)if(~f[d][s]){ 41 for(register int j=1;j<=m;++j) 42 if(bad[i][j])cur+=2*bin[j-1],now[j]=2; 43 else cur+=bin[j-1]*(now[j]=_max((pre[j]=s/bin[j-1]%3)-1,0)); 44 dp(1,f[d][s],cur);f[d][s]=-1; 45 } 46 } 47 for(register int i=0;i<bin[m];++i)MAX(ans,f[d][i]); 48 printf("%d\n",ans); 49 } 50 return 0; 51 }
有一位神仙用二进制写出来了,受我一拜。。。