1. 程式人生 > >BZOJ 3336 Black and White (插頭DP)

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\n",ans);
206     }
207     return 0;
208 }