题目:click
可以先看看陈丹琦的论文:基于连通性状态压缩的动态规划问题
插头dp中的基本思想以及状态划分转移都有写入在上述论文中。
对于此类范围不大,有连通性要求的网格图可以考虑采用插头dp去求解问题。
平时普通的状压dp只是保有染色状态,此题目中还加入了需考虑的网格连通性问题。
题意:给定一个网格图,有一些已经染色黑,白状态,染黑色的部分要保持连通性,白色也是,还要保证没有一个2*2的格子颜色都相同,问全部染色的方案数,可行的话输出一种可行的方案。
博主由于太菜借鉴了别人的代码,详细注释在代码中。
代码来源:click
如果上方格子与当前格子的颜色不一样并且上方格子是单独的连通量,不合理,由于之后连通分量全一样的话会造成2 * 2不合理情况(前提是不在最后两列最后两行)。
如果上方和左方的和当前颜色都一样,将上方左方连起来。
考虑到需要输出一种可行的方案,只需要用一个64位的二进制数表示记录就可以了。写起来还是比较多的注意细节。。。
#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<istream>
#include<vector>
#include<stack>
#include<set>
#include<map>
#include<algorithm>
#include<queue>
#define inf 0x3f3f3f3f
#define MAX_len 50100*4
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int MAXN=10010;
int n,m,cur;
int g[10][10];
int num[10];//最小表示
int code[10],color[10];//code:所属的连通分量 color:染色状态情况
char anss[10][10];//记录任意一符合的答案
struct A {
static const int mod=100003;比实际容量大的素数
int head[mod];//指针链
int next[MAXN];//指针链
ll state[MAXN];//状态
ll value[MAXN];//状态对应dp的值
int Size;
ll path[MAXN];//记录路径
void Clear()
{
memset(head,-1,sizeof(head));
Size=0;
}
void Insert(ll st,ll val,ll p)//st是已经压缩的编码
{
int h=st%mod;
for(int i=head[h];i!=-1;i=next[i])
{
if(state[i]==st)//如果这个值已经存在
{
value[i]+=val;//直接加上这种情况的个数
path[i]=p;
return ;
}
}
state[Size]=st;
value[Size]=val;
path[Size]=p;
next[Size]=head[h];
head[h]=Size++;
}
}dp[2];
ll encode()//压缩编码
{
memset(num,-1,sizeof(num));
ll s=0;
for(int i=0,cnt=0;i<m;i++)
{
if(num[code[i]]<0)
num[code[i]]=cnt++;
s<<=4;//最多只有8个状态 用最高位来表示颜色就压缩好了
s+=(num[code[i]]&7)+color[i]*8;//最高位为颜色 低三位是轮廓线
}
s<<=1;
s+=color[m];//方便解码处理m
return s;
}
void decode(ll s)//解码
{
color[m+1]=(s&1);//左上方的格子状态
s>>=1;
for(int i=m;i>=1;i--)//是从当前格子 直接从1开始
{
code[i]=(s&7);
s>>=3;
color[i]=(s&1);//高位是颜色
s>>=1;
}
}
inline bool isunique()//判断一个格子上的连通分量是不是唯一的
{
for(int i=1;i<m;i++)
if(code[i]==code[m])
return false;
return true;
}
inline bool samecolor()//轮廓线上除了m外,其他的格子是否同色的
{
for(int i=1;i<m;i++)
{
if(color[i]!=color[i-1])
return false;
}
return true;
}
void comb(int c,int j)//合并两个连通分量
{
if(c==color[m])
code[0]=code[m];//向上方的连通分量看齐 首先得保证上方不丢失
else
code[0]=7;//新开一个
if(j&&c==color[1])//向左边看齐
{
int temp=code[0];
for(int i=0;i<m;i++)
if(code[i]==temp)
code[i]=code[1];
}
}
void trycover(int c,ll v,ll p,int i,int j)//对i,j上色
{
if(j&&color[1]==c&&color[m+1]==c&&color[m]==c)
//2*2的格子同色不合理
return ;
color[0]=c&1;
if(i==n-1&&j==m-2)//对最后2格特殊判断
{
if(c==color[m]||!isunique()||samecolor())
{
comb(c,j);
dp[cur].Insert(encode(),v,p);
}
}
else if(i==n-1&&j==m-1)//最后一格
{
if(c==color[m]||!isunique()||samecolor())
{
if(i<2||c==color[m]||c==color[1])//行为2的网格也需要处理他最后一个可以形成独立的连通量
{
comb(c,j);
dp[cur].Insert(encode(),v,p);
}
}
}
else
{
if(c!=color[m]&&isunique())
return ;
comb(c,j);
dp[cur].Insert(encode(),v,p);
}
}
void solve()
{
cur=0;
dp[cur].Clear();
for(int i=0;i<(1<<m);i++)//首先处理第一行的情况
{
color[m]=0;
bool flag=1;
int temp=i;
for(int j=0;j<m;j++)
{
color[j]=(temp&1);//放入染色情况
if(g[0][m-1-j]<2&&g[0][m-1-j]!=color[j])//判断是否符合
flag=0;
temp>>=1;
}
if(flag)
{
code[m-1]=0;//所属连通分量
for(int j=m-2,up=0;j>=0;j--)
{
if(color[j]==color[j+1])
code[j]=code[j+1];
else
code[j]=++up;
}
dp[0].Insert(encode(),1,i);
}
}
for(int i=1;i<n;i++)
{
for(int j=0;j<m;j++)
{
cur^=1;
dp[cur].Clear();
for(int k=0;k<dp[cur^1].Size;k++)
{
ll s=dp[cur^1].state[k];
ll v=dp[cur^1].value[k];
ll p=dp[cur^1].path[k];
decode(s);//解码
p<<=1;//扩展一位做记录
if(g[i][j]==2)
{
trycover(0,v,p+0,i,j);
trycover(1,v,p+1,i,j);
}
else
trycover(g[i][j],v,p+g[i][j],i,j);
}
}
}
}
void print()
{
int ans=0;
ll temp=0;
for(int i=0;i<dp[cur].Size;i++)
{
int big=0;
decode(dp[cur].state[i]);
for(int y=1;y<=m;y++)
big=max(big,code[y]);
if(big<=1)
{
temp=dp[cur].path[i];
ans+=dp[cur].value[i];
}
}
printf("%d\n",ans);
if(ans)
{
memset(anss,'\0',sizeof(anss));
for(int i=n-1;i>=0;i--)
{
for(int j=m-1;j>=0;j--)
{
if(temp&1)
anss[i][j]='#';
else
anss[i][j]='o';
temp>>=1;
}
}
for(int i=0;i<n;i++)
{
printf("%s\n",anss[i]);
}
}
printf("\n");
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int i,j,k;
scanf("%d %d",&n,&m);
getchar();
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
char temp;
scanf("%c",&temp);
if(temp=='#')//黑色
g[i][j]=1;
if(temp=='o')//白色
g[i][j]=0;
if(temp=='.')//可放的方块
g[i][j]=2;
}
getchar();
}
solve();
print();
}
return 0;
}