• BZOJ 3336 Black and White (插头DP)


    题目大意:

    给你一个n×m的网格,有一些格子已经被涂上了白色或者黑色,让你用黑色或白色填剩下的格子,且填好的网格必须保证:

    1.对于任意2×2的子矩阵的4个格子,它们的颜色不能都相同

    2.所有黑色的块连在一起,所有白色块连在一起(四联通)

    2<=n,m<=8

    搞了2天终于过了

    参考了这位神犇的博客http://www.cnblogs.com/staginner/archive/2012/09/17/2688634.html

    很duliu的一道插头DP

    涂色需要满足的条件有很多神奇的性质

    由于一个联通块在轮廓线里可能出现不止一次,如果用括号匹配做,会非常麻烦,三进制也不好用了,用4进制会过于恶心

    “最小表示法”

    这是表示轮廓线的另一种方法,名字叫“最小表示法”,其实和字符串的最小表示法没多大联系,因为轮廓线并不能像成环的字符串那样滚动

    但思想是类似的

    轮廓线上处于同一联通块的格子编号相同

    用尽可能小的数来表示状态,比如在某次状态更新后,新的状态表示为23333,那么重新编码后,状态变成了12222

    可以用一个桶来实现

    通过最小表示法,达到了我们“动态规划”合并等价状态的需要

    然后把重新编码后的状态压缩成一个数,我用了十进制,虽然慢一点但特别好调

    利用卓越的哈希技术,我们就能把这个数存下来了

    由于2*2的田字形不能都同色,所以需要额外记录左上方格子的颜色

    还要记录每个格子的颜色

    由于相邻的相同颜色的格子必然处于同一联通块中

    所以我们只记录第一个格子的颜色,后面格子的颜色就可以利用上面的性质直接推出来了

    现在,我们能表示出轮廓线了

    等等,左上,上,左,还要它自己,一共4个格子,岂不是要讨论2^4=16种情况??

    复杂讨论的优化

    优化1

    由于题目中并没有对白色或黑色进行单独的额外限制,仅仅是一些格子必须填上白色或者黑色

    所以我们在外层讨论向这个格子里填白色还是黑色就行了

    里层函数只需分析 周围的格子的颜色 和 这个格子要填的颜色 是否同色,不同色为1,同色为0

    这样优化掉了一维,只需要讨论8次了

    优化2

    以当前格子填黑色为例

    1.上方格子无法在后续操作被合并

    每当我们遍历到一个格子的时候,如果上方的格子是白色

    和轮廓线上其他(除了左上)的白色格子都不连通

    显然在轮廓线右移之后,上面的格子也不能再和其他的格子联通了

    显然这种情况不合法

    如果轮廓线上没有其他的白色格子,说明它是轮廓线上唯一一个联通块

    由于2×2的格子不能同色,所以这种情况合法的充要条件是,现在的格子是最后两个格子(n,m)和(n,m-1)

    2.左侧格子无法在后续操作被合并

    显然这种情况只会发生在最后一排

    为了减少讨论,我们无视这种情况

    仅在遍历到最后一个格子(n,m)统计答案时,重新扫一遍轮廓线,同一颜色的格子应该都处于一个联通块中,即标号相同

    我们又略去了左上方格子无法被联通的情况

    特判

    特判1

    那么每一行开头的格子该怎么处理呢

    把上一行的状态全都去掉第m+1格子,然后右移,把第一个格子的状态空出来

    这样达到了保留左上方格子状态的目的

    开头的格子需要额外的特判,讨论是否把上方的格子给堵住了

    特判2

    第一行该怎么办呢

    搜2^m个的状态,检查是否合法,然后全扔到哈希表里

    细节比较多

      1 #include <cmath>
      2 #include <cstdio>
      3 #include <cstring>
      4 #include <algorithm>
      5 #define N1 12
      6 #define M1 50010
      7 #define ll long long
      8 #define uint unsigned int
      9 #define il inline 
     10 #define jr 100
     11 using namespace std;
     12 
     13 int T,n,m,now=1,pst=0,ne;
     14 int bar[N1];
     15 char str[N1][N1];
     16 
     17 ll zip(int *a)
     18 {
     19     memset(bar,-1,sizeof(bar));
     20     ll ans=0;int cnt=0;
     21     ans=a[0];
     22     for(int i=1;i<=m+1;i++)
     23     {
     24         if(bar[a[i]]==-1) bar[a[i]]=++cnt;
     25         ans=ans*10+bar[a[i]];
     26     }
     27     return ans;
     28 }
     29 void unzip(int *a,int *b,ll s,int &ma)
     30 {
     31     for(int i=m+1;i>=0;i--)
     32         a[i]=s%10,s/=10,ma=max(ma,a[i]);
     33     b[1]=a[0];
     34     for(int i=2;i<=m+1&&a[i];i++)
     35         b[i]=(a[i]==a[i-1])?b[i-1]:b[i-1]^1;
     36 }
     37 
     38 struct Hash{
     39 int head[M1],nxt[M1],cte;
     40 ll val[M1],to[M1];
     41 void clr(){memset(head,0,sizeof(head));cte=0;}
     42 void ae(int u,int v,int w)
     43 {
     44     cte++;to[cte]=v,val[cte]=w;
     45     nxt[cte]=head[u],head[u]=cte;
     46 }
     47 int find(ll x)
     48 {
     49     ll y=x%jr,v;
     50     for(int j=head[y];j;j=nxt[j]){
     51         v=to[j];
     52         if(v==x) return j;
     53     }return 0;
     54 }
     55 void ins(ll x,ll w)
     56 {
     57     int i=find(x);
     58     if(!i) ae(x%jr,x,0),i=cte;
     59     val[i]+=w;
     60 }
     61 }h[2];
     62 
     63 int tmp[N1],a[N1],bin[N1];
     64 int check(int l,int p,int r)
     65 {
     66     for(int i=1;i<=l;i++)
     67         if((tmp[i]==tmp[p])) return 0;
     68     for(int i=r;i<=m+1;i++)
     69         if((tmp[i]==tmp[p])) return 0; //存在同一联通块
     70     return 1; //不存在
     71 }
     72 
     73 ll ans=0;
     74 
     75 void calc(int x,int y,int f)
     76 {
     77     ll s,t;
     78     int typ[2]={1,1},c[3],ma,mi,fl,cnt,w;
     79     for(int k=0;k<jr;k++)
     80     for(int p=h[pst].head[k];p;p=h[pst].nxt[p])
     81     {
     82         s=h[pst].to[p],ma=0;
     83         unzip(a,bin,s,ma);
     84         c[0]=(y!=1)?(bin[y-1]^f):0,c[1]=(y!=1)?(bin[y]^f):0,c[2]=bin[y+1]^f;
     85         for(int i=0;i<=m+1;i++) tmp[i]=a[i];
     86         /*if(x==2&&y==3)
     87             mi=1;*/
     88         w=h[pst].val[p];
     89         if(y==1){
     90             if(!c[2]) tmp[y]=tmp[y+1];
     91             else{
     92                 if(check(0,y+1,y+2)) continue;
     93                 tmp[y]=0,tmp[0]=f;
     94             }
     95         }else if(c[0]&&c[1]&&c[2]){ // 111 直接创建新联通块
     96             tmp[y]=ma+1;
     97         }else if(!c[0]&&c[2]){ // 001 011 
     98             if(check(y-2,y+1,y+2)){ //轮廓线上没有和上边相连的块
     99                 fl=1;
    100                 for(int i=y-2;i>=1;i--)
    101                     if(bin[i]==bin[y+1]) fl=0;
    102                 for(int i=y+2;i<=m+1;i++)
    103                     if(bin[i]==bin[y+1]) fl=0; //轮廓线上存在其他1联通块,无法合并
    104                 if(!fl) continue;
    105                 if(!(x==n&&y==m-1)&&!(x==n&&y==m)) continue; //不是最后两个格子,不合法
    106             }
    107             tmp[y]=tmp[y-1];
    108         }else if(c[0]&&!c[2]){ // 100 110 左上的点不用关心,必然连接左边或者上边,且是从上往下遍历的,直接合并即可,如果不合法,也可以最后再检查
    109             tmp[y]=tmp[y+1];
    110         }else if(!c[0]&&c[1]&&!c[2]){ // 010 由于之前001 011里保证过上方格子能联通,这次就不用再检查左上方了,合并联通块,如果没联通,除非是最后一个点,否则不合法
    111             tmp[y]=tmp[y+1]=tmp[y-1]=min(tmp[y+1],tmp[y-1]);
    112             for(int i=y+2;i<=m+1;i++) //合并联通块
    113                 if(a[i]==a[y+1]) tmp[i]=tmp[y];
    114             for(int i=y-2;i>=1;i--)   //合并联通块
    115                 if(a[i]==a[y-1]) tmp[i]=tmp[y];
    116         }else if(c[0]&&!c[1]&&c[2]){  // 101
    117             if(x==n&&y==m) continue; //最后一个点,必然不合法
    118             if(check(y-1,y+1,y+2)){continue;} //即将越过这个点,如果没联通,之后也不能再联通了
    119             tmp[y]=ma+1;
    120         }else{ // 000 
    121             continue;
    122         }
    123         t=zip(tmp);
    124         if(x==n&&y==m)
    125         {
    126             cnt=0,ma=0;
    127             unzip(a,bin,t,ma);
    128             memset(bar,0,sizeof(bar));
    129             for(int i=1;i<=m+1;i++)
    130                 if(!bar[a[i]]) bar[a[i]]=1,cnt++;
    131             if(cnt<=2) 
    132                 ans+=h[pst].val[p];
    133             continue;
    134         }
    135         h[now].ins(t,h[pst].val[p]);
    136     }
    137 }
    138 void Enter()
    139 {
    140     h[now].clr();ll s,t;int ma;
    141     for(int k=0;k<jr;k++)
    142     for(int p=h[pst].head[k];p;p=h[pst].nxt[p])
    143     {
    144         s=h[pst].to[p],ma=0;
    145         unzip(a,bin,s,ma);
    146         for(int i=0;i<=m+1;i++) tmp[i]=a[i];
    147         for(int i=m+1;i>=2;i--) tmp[i]=tmp[i-1];
    148         tmp[1]=tmp[2];
    149         t=zip(tmp);
    150         h[now].ins(t,h[pst].val[p]);
    151     }
    152     swap(now,pst);
    153 }
    154 void Pre()
    155 {
    156     int fl,cnt;ll t;
    157     h[now].clr();
    158     for(int s=0;s<(1<<m);s++)
    159     {
    160         fl=1,cnt=0;
    161         for(int i=0;i<m;i++){
    162             if((s&(1<<i))&&str[1][i+1]=='o') {fl=0;break;}
    163             if(!(s&(1<<i))&&str[1][i+1]=='#') {fl=0;break;}
    164             bin[i+1]=(s>>i)&1;
    165         }
    166         if(!fl) continue;
    167         tmp[0]=bin[1],tmp[2]=++cnt;
    168         for(int i=2;i<=m;i++){
    169             if(bin[i]==bin[i-1]) tmp[i+1]=tmp[i];
    170             else tmp[i+1]=++cnt;
    171         }tmp[1]=tmp[2];
    172         t=zip(tmp);
    173         h[now].ins(t,1);
    174     }
    175     swap(now,pst);
    176 }
    177 
    178 int main()
    179 {
    180     //freopen("t2.in","r",stdin);
    181     scanf("%d",&T);
    182     while(T--)
    183     {
    184         ans=0; 
    185         scanf("%d%d",&n,&m);
    186         for(int i=1;i<=n;i++)
    187             scanf("%s",str[i]+1);
    188         Pre();
    189         ne=-1;
    190         if(str[n][m]=='#') ne=0;
    191         if(str[n][m]=='o') ne=1;
    192         for(int i=2;i<=n;i++)
    193         {
    194             for(int j=1;j<=m;j++)
    195             {
    196                 h[now].clr();
    197                 if(str[i][j]!='#')
    198                     calc(i,j,0);
    199                 if(str[i][j]!='o')
    200                     calc(i,j,1);
    201                 swap(now,pst);
    202             }
    203             Enter();
    204         }
    205         printf("%lld
    ",ans);
    206     }
    207     return 0;
    208 }
  • 相关阅读:
    Linux中配置Aria2 RPC Server
    Ubuntu无法进入Windows的NTFS分区
    Visualbox在UEFI模式下无法正常引导
    pacman安装软件包出现损坏
    Windows下禁用锁屏热键WinKey+L
    Linux中无权限使用sudo
    Windows 10 MBR转GPT
    oh-my-zsh的安装与基本配置
    Raspbian开启root账户
    xrandr: 命令行修改分辨率工具
  • 原文地址:https://www.cnblogs.com/guapisolo/p/10086510.html
Copyright © 2020-2023  润新知