UVA10572 Black & White 题解
插头 DP
Statement
UVA10572 Black & White - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Solution
因为相邻格子连通性取决于颜色而不取决于几联通,所以我们需要记录轮廓线上方 \(m\) 个格子的颜色和连通性
注意区分颜色和连通性的区别,颜色相同不一定联通
同时注意到题面的特殊限制,所以需要保存 \((i-1,j-1)\) 的颜色
连通性需要使用广义括号表示法/最小表示法表示,这里采用最小表示法
联通状态最多有 8 种不同的,直接上 8 进制即可。
即,设 \(f(i,j,sta,col,cp)=val\) 表示正在涂色 \((i,j)\) ,轮廓线连通性 $sta $ ,颜色 \(col\) ,\((i-1,j-1)\) 颜色 \(cp\) 的方案数
考虑转移,设 \(u,l,lu\) 分别表示 \((i,j)\) 颜色是否和 上面/左边/左上 格子颜色相同
容易 \(O(1)\) 推知下一次的 \(col^{\prime},cp^{\prime}\) ,关键在于连通性,解码 \(sta\to a\)
- \(u\wedge l\) ,合并联通块 \(a[j-1],a[j]\) ,把所有 \(a[x]=a[j]\) ,刷成 \(a[x]=a[j-1]\)
- \(u\wedge !l\) ,连通性没有变化
- \(!u\wedge l\) ,\(a[j]=a[j-1]\)
- \(!u\wedge !l\) ,自立山门,\(a[j]=mx+1\)
重新编码的时候注意要满足最小表示法。
显然,如果 \(u\wedge l\wedge lu\) ,那就直接 G 了
考虑什么时候也会 GG:
- \(i==n \wedge j==m\wedge !l\wedge !u\wedge lu\)
- \(!u\) 且 \(u\) 在轮廓线上独立成一个连通块 且 轮廓线上存在其他任意个与 \(u\) 颜色相同但的格子
- \(!u\) 且轮廓线上不存在一个与 \(u\) 颜色相同的格子 且 \((i,j)\) 不是棋盘最后两个格子
第二条:可以考虑试填法,\((i,j-1)\) 位必然和 \((i,j)\) 相同。因为如若不相同,那么 \((i-1,j-1)\) 必然与 \((i,j)\) 相同,卡死了。同时 \((i-1,j+1)\) 位也和 \((i,j)\) 相同。因为其他格子和 \((i-1,j)\) 连通性都不同,容易发现 \((i-1,j)\) 被孤立,GG
第三条:很显然,此时轮廓线下方格子应该全部都涂黑
输出方案的话在 DP 过程中记录一个 pre 就可以了
Code
小记:写了一个晚上,c。下午 4:30 左右开始思考(穿插看 ppt 和题解),5:30 大致理解。吃完饭 6:30 开始按照自己的理解写,写到 \(9:00\) ,死活不对,开始看题解代码
漏掉的细节:
- 边界条件应该直接认为是不同颜色,不能认为是相同颜色
- 由于直接认定为不同,所以并不需要像【模板】那样,开始之前 \(que<<=3\)
- 虽然你有 hash_map ,你可以直接使用十进制编码,但是不管什么进制,每次在最小表示法的时候一定要重编号(为啥会认为十进制就不用重编号...
- 取答案的时候不要直接 判断都不上/乱判断 直接取(为啥会判断成 \(mx==0\) 才行。。。)
- 请认真分析什么时候会 GG ,不要 yy 过头
#include<bits/stdc++.h>
#define bit(x) (1<<(x))
using namespace std;
typedef long long ll;
const int mod = 233333;
const int M = mod + 5;
const int N = 20;
int read(){
int s=0,w=1; char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
int a[N],num[N],pre[100][mod+5];
char mp[N][N],op[100][mod+5];
int T,n,m,now;
struct Hash_map{
int nex[M],head[M];
int sta[M],col[M],val[M];//二进制表示 col ,八进制表示 sta
int siz;
void clear(){
memset(head,0,sizeof(head));
siz=0;
}
void insert(int s,int c,int v,int id,int fa,char o){
for(int i=head[s%mod];i;i=nex[i])
if(sta[i]==s&&col[i]==c)
return val[i]+=v,void();
sta[++siz]=s,col[siz]=c,val[siz]=v;
pre[id][siz]=fa,op[id][siz]=o;
nex[siz]=head[s%mod],head[s%mod]=siz;
}
}dp[2];
void decode(int s){//解码
for(int i=m-1;~i;--i)
a[i]=s&7,s>>=3;
}
int encode(){//最小表示法
memset(num,-1,sizeof(num));
int k=-1,res=0;
for(int i=0;i<m;++i){
if(num[a[i]]==-1)num[a[i]]=++k;
res=(res<<3)|num[a[i]];
}
return res;
}
void change(int x,int y){//刷颜色
for(int i=0;i<m;++i)
if(a[i]==x)a[i]=y;
}
void DP(int i,int j,int c){
for(int k=1;k<=dp[now].siz;++k){
int cc=dp[now].col[k];
int u=i?(cc>>j&1)==c:0;
int l=j?(cc>>(j-1)&1)==c:0;//边界直接认为不相同
int lu=(cc>>m)==c;// 二进制第 m 位存放 (i-1,j-1)
if(u&&l&&lu)continue;
if(i==n-1&&j==m-1&&!u&&!l&&lu)continue;
decode(dp[now].sta[k]);
if(i&&!u){
int s1=0,s2=0;
for(int t=0;t<m;++t)
s1+=a[t]==a[j],s2+=((cc>>t&1)!=c);
//s1:连通性相同格子个数, s2: 与 u 颜色相同个数
if(s1==1){
if(s2>1)continue;
if(i<n-1||j<m-2)continue;
}
}
if(u&&l){
if(a[j]!=a[j-1])
change(a[j],a[j-1]);
}else if(!u&&l)a[j]=a[j-1];
else if(!u&&!l)a[j]=m;
if(cc&(1<<j))cc|=1<<m; else cc&=~(1<<m);
if(c)cc|=1<<j; else cc&=~(1<<j);
dp[now^1].insert(encode(),cc,dp[now].val[k],i*m+j,k,c?'#':'o');
}
}
void print(int k){
for(int i=n-1;i>=0;--i)
for(int j=m-1;j>=0;--j)
mp[i][j]=op[i*m+j][k],
k=pre[i*m+j][k];
for(int i=0;i<n;++i)puts(mp[i]);
}
signed main(){
T=read();
while(T--){
n=read(),m=read();
memset(mp,0,sizeof(mp));
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
scanf(" %c",&mp[i][j]);
now=0,dp[0].clear(),dp[0].insert(0,0,1,0,0,0);
for(int i=0;i<n;++i)
for(int j=0;j<m;++j){
dp[now^1].clear();
if(mp[i][j]!='#')DP(i,j,0);
if(mp[i][j]!='o')DP(i,j,1);
now^=1;
}
int ans=0,k;
for(int i=1;i<=dp[now].siz;++i){
int mx=0;
decode(dp[now].sta[i]);
for(int j=0;j<m;++j)
mx=max(mx,a[j]);
if(mx>1)continue;
ans+=dp[now].val[i];
k=i;
}
printf("%d\n",ans);
if(ans)print(k);
puts("");//UVA 神奇要求
}
return 0;
}