zoukankan      html  css  js  c++  java
  • 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
    ",ans);
    49     }
    50     return 0;
    51 }
    View Code

    有一位神仙用二进制写出来了,受我一拜。。。

  • 相关阅读:
    自制游戏Zombie代码
    HNOI2020总结
    20200615题解:继续扮演
    20200611题解:树网的核
    历次考试总结
    寒假总结和省选大体规划
    每日总结
    有一种感动叫ACM(记WJMZBMR在成都赛区开幕式上的讲话)
    递推求欧拉函数的最简单的详解
    总结一些好用的C++小技巧
  • 原文地址:https://www.cnblogs.com/saigyouji-yuyuko/p/11543585.html
Copyright © 2011-2022 走看看