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 }