记录编号 87048 评测结果 AAAAAAAAAA
题目名称 [UVa 10572] 黑和白 最终得分 100
用户昵称 Gravatarcstdio 是否通过 通过
代码语言 C++ 运行时间 0.125 s
提交时间 2014-01-30 16:22:29 内存使用 0.32 MiB
显示代码纯文本
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<queue>
#include<deque>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
//规定黑为1白为0
//采用一个64位整数储存,从左到右依次排放:8bit轮廓线上的格子颜色,24bit轮廓线上格子连通状态的最小表示法
//把这个64位整数叫做"状态码"
//在轮廓线上的格子颜色和连通状态的最小表示法中,从低位到高位依次存储从左到右的格子状态
//本题的下标从0开始
const int SIZEN=10;
const ll comp_ext=(1<<24)-1;//将状态码按位与这个数后得到连通状态的最小表示法
const ll col_ext=(1<<8)-1;//将状态码右移24位,按位与这个数得到轮廓线上格子颜色
int ROW,COLU;//行数和列数
class STATE{
public:
	bool color[SIZEN];//轮廓线上格子颜色
	bool upleft;//左上方格子颜色
	short comp[SIZEN];//各格子的连通分量
	short numcomp;//连通分量总数
	short ncolorcomp[2];//白黑连通分量个数
	void relable(void){//将comp(其实是一个深度为1的并查集)改成最小表示法
		//这实际上是一个重新指定连通分量编号的过程
		int atp[SIZEN]={0};
		memset(atp,-1,sizeof(atp));
		numcomp=ncolorcomp[0]=ncolorcomp[1]=0;
		for(int i=0;i<COLU;i++){
			if(atp[comp[i]]==-1){//如果这个连通分量尚未标号
				atp[comp[i]]=numcomp++;
				ncolorcomp[color[i]]++;
			}
			comp[i]=atp[comp[i]];
		}
	}
	void merge(int a,int b){//将所有编号为b的连通分量改成a
		if(a==b) return;
		for(int i=0;i<COLU;i++){
			if(comp[i]==b) comp[i]=a;
		}
	}
	void clear(void){
		memset(color,0,sizeof(color));
		upleft=0;
		memset(comp,0,sizeof(comp));
		numcomp=0;
		memset(ncolorcomp,0,sizeof(ncolorcomp));
	}
	ll mdl(void){//调制成状态码
		ll com=0,col=0;
		ll ans=0;
		for(int i=COLU-1;i>=0;i--) col=(col<<1)+color[i];
		ans=(ans<<8)+col;
		for(int i=COLU-1;i>=0;i--) com=(com<<3)+comp[i];
		ans=(ans<<24)+com;
		return ans;
	}
};
int board[SIZEN][SIZEN]={0};
bool nowgrid[SIZEN][SIZEN]={0},ansgrid[SIZEN][SIZEN]={0};
map<ll,int> f[SIZEN][SIZEN][2];
bool legal=0;//是否存在合法方案
int DP(int x,int y,STATE &S,int forcecol){
	if(y==COLU){y=0;x++;}//计算行末相当于计算下一行初
	S.relable();
	if(x==ROW){//已经计算完成了
		if(S.ncolorcomp[0]>1||S.ncolorcomp[1]>1) return 0;//未成连通块
		if(!legal){
			legal=true;
			for(int i=0;i<ROW;i++){
				for(int j=0;j<COLU;j++) ansgrid[i][j]=nowgrid[i][j];
			}
		}
		return 1;
	}
	//如果左和上不同色,左上不必考虑,认为是0
	if(x&&y&&S.color[y]!=S.color[y-1]) S.upleft=0;
	ll scode;
	if(forcecol<0){//如果不指定颜色就试图调用之前的状态
		scode=S.mdl();
		if(f[x][y][S.upleft].count(scode)) return f[x][y][S.upleft][scode];
	}
	int ans=0;
	for(int nowc=0;nowc<=1;nowc++){
		if(forcecol==1-nowc||board[x][y]==1-nowc) continue;//与确定的冲突,这旨在让指定颜色的状况也是用不指定颜色的计算通道,从而减少代码量
		if(x&&y&&S.color[y-1]==nowc&&S.color[y]==nowc&&S.upleft==nowc) continue;
		STATE T=S;
		T.color[y]=nowc;//将其染色
		T.upleft=S.color[y];//得出左上方格子的状态
		T.comp[y]=S.numcomp;//标号设为S的最大连通分量编号+1
		if(x>0&&S.color[y]==nowc) T.comp[y]=S.comp[y];//若与上方格子颜色相同,则它们处于同一分量
		nowgrid[x][y]=nowc;
		if(y>0&&T.color[y-1]==nowc) T.merge(T.comp[y-1],T.comp[y]);//若和左边颜色相同,则合并
		if(x>0&&S.color[y]==1-nowc){//如果上方格子以后无法在以后连上
			if(find(T.comp,T.comp+COLU,S.comp[y])==T.comp+COLU){//如果该连通分量消失
				if(S.ncolorcomp[1-nowc]>1||x<ROW-2) continue;
				//如果还有其他这个颜色的连通分量,或还有至少两行,显然是不合法的
				ans+=DP(x,y+1,T,nowc);//以后强制涂nowc
				continue;
			}
		}
		ans+=DP(x,y+1,T,forcecol);
	}
	if(forcecol<0){
		f[x][y][S.upleft][scode]=ans;
	}
	return ans;
}
int main(){
	freopen("blackandwhite.in","r",stdin);
	freopen("blackandwhite.out","w",stdout);
	int T;
	//scanf("%d",&T);
	T=1;
	for(int kase=1;kase<=T;kase++){
		scanf("%d%d",&ROW,&COLU);
		for(int i=0;i<ROW;i++){
			string str;
			cin>>str;
			for(int j=0;j<COLU;j++){
				if(str[j]=='o') board[i][j]=0;
				else if(str[j]=='#') board[i][j]=1;
				else if(str[j]=='.') board[i][j]=-1;
			}
		}
		STATE S;
		S.clear();
		S.relable();
		for(int i=0;i<8;i++){
			for(int j=0;j<8;j++){
				for(int k=0;k<2;k++) f[i][j][k].clear();
			}
		}
		legal=0;
		printf("%d\n",DP(0,0,S,-1));
		/*if(legal){
			for(int i=0;i<ROW;i++){
				for(int j=0;j<COLU;j++){
					if(ansgrid[i][j]==0) cout<<"o";
					else if(ansgrid[i][j]==1) cout<<"#";
				}
				cout<<endl;
			}
		}
		cout<<endl;*/
	}
	return 0;
}