uva 10572 Black & White 插头dp


题意:给出一个m*n的棋盘,(2<=m,n<=8),其中有些格子白色,有些黑色,有些未涂色,现在要你将未涂色的格子涂色,使得白色格子只有一个连通分量,黑色格子也只有一个连通分量,并且不能出现2*2的矩形满足内部4个格子同色。

问涂色方法数。如果答案>0,输出任意一种涂色方案。


解法:轮廓线上的动态规划,我学着书上写就已经很累了。


#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;

#define all(x) (x).begin(), (x).end()
#define for0(a, n) for (int (a) = 0; (a) < (n); (a)++)
#define for1(a, n) for (int (a) = 1; (a) <= (n); (a)++)
typedef long long ll;
typedef pair<int, int> pii;
const int INF =0x3f3f3f3f;
const int maxn= 8   ;
int nrows,ncols;
char a[maxn][maxn],ans[maxn][maxn],grid[maxn][maxn];
bool solve;
struct State
{//数量很小,用char完全装的下。
    char color[8];
    char up_left;
    char ncomps,comp[8];
    char color_ncomps[2];
    void normalize()
    {
        int ret[8];
        memset(ret,-1,sizeof ret);
        ncomps=color_ncomps[0]=color_ncomps[1]=0;
        for0(i,ncols)
        {
           if(ret[comp[i]]==-1)
           {
              ret[comp[i]]=ncomps++;
              color_ncomps[color[i]]++;
           }
           comp[i]=ret[comp[i]];
        }
    }
    unsigned int encode()
    {//unsigned int 能表示值的个数是2^32,这里最大为16^8=(2^4)^8=2^32,刚好装下。
        unsigned int key=0;
        for0(i,ncols) {
            key=key*16+8*color[i]+comp[i];
        }
        return key;
    }
    void merge(int a,int b)
    {
        if(b==a )  return;//省时间
        for0(i,ncols)
        {
            if(comp[i]==b )  comp[i]=a;
        }
    }
};

map<unsigned ,int >dp[maxn][maxn][2];//unsigned int,int
const char ch[2]={'o','#'};
/*
对于无限制的用dp保留,对于有限制的直接搜搜。
force_color如果为-1代表没有限制,如果为0或1代表必须要涂0或1这种颜色。
*/
int DP(int row,int col,State & S ,int force_color )
{

    if(col==ncols) row++,col=0;

    S.normalize();//最小表示
    if(row==nrows)
    {
        if(S.color_ncomps[0]>1||S.color_ncomps[1]>1)  return 0;
        //这句话似乎是防止那些轮廓线变化时没有消失连通分量但是又不满足题意的情况。
        if(!solve)
        {
            for0(i,nrows) for0(j,ncols)  ans[i][j]=grid[i][j];
            solve=true;
        }
        /**
        ans其实是记录了第一种可行解,因为这完全是一种搜索,深度优先搜索。
        **/
       return 1;
    }

    /**底下是一个强力剪枝,书上的代码说,如果上面的格子和左侧的格子颜色不一样,那么统一设为0
    ,这个可以减少状态,大大提高速率。这句话一定要放在判断是否计算过前,且只针对无限制情况有用。

    但要注意条件必须是row和col>0时才能用
**/

    if(row>0&&col>0&&S.color[col]!=S.color[col-1]) S.up_left=0;

    unsigned int key;if(force_color<0)  key=S.encode();

    if(force_color<0&&dp[row][col][S.up_left].count(key)) return dp[row][col][S.up_left][key];
    //状态的表示并不考虑该位置涂哪种颜色,而是在该位置两种都可以涂的情况下,一起考虑后的种数。

   /*
    int &ret=dp[row][col][S.up_left]; 一般记忆化搜索的这种写法,这里不行,因为dp[row][col][S.up_left]
    表示的是没有限制时的种数。后面要考虑限和不限制的。

   */

   //真正的状态表示其实是dp[row][col][S.up_left][S中的color和comp数组];
    int ret=0;
    for(int color=0;color<2;color++)
    {
        if( force_color==  (color^1)  ) continue;
//        if( force_color==color^1  )   continue;  ^优先级竟然比==还低
        if(a[row][col] ==ch[color^1] )  continue;
      //颜色0 1 表示黑 白,那么只有当原来的颜色(或者不涂)正好和现在填的相反 才是不行的
        if(row>0&&col>0&&color==S.color[col]&&color==S.color[col-1]&&color==S.up_left)  continue;

        grid[row][col]=ch[color];

        State T=S;
        T.color[col]=color;
        T.up_left=S.color[col];


/*
  两行,merge,归并。
*/
        T.comp[col]= row>0&&S.color[col]==color?S.comp[col]:S.ncomps;//容易掉row>0
        if(col>0&&S.color[col-1]==color) T.merge( T.comp[col-1] ,T.comp[col] );



        if(row>0&&S.color[col]!=color) {
        /**S.color[col]!=color这句话是不能掉的,因为仅凭
        find(T.comp,T.comp+ncols,S.comp[col])==T.comp+ncols这句话
        不足以判断将推出轮廓线的格子是否是一个独立的连通分量。
        color相等,一定不是。
        color不相等,find(T.comp,T.comp+ncols,S.comp[col])==T.comp+ncols,即即使T中找不到
        与S.col相同编号的格子,退出的格子也可能与其中之一连通,原因是退出的格子在T中的编号被改了。
        如果S.color[col]!=color,那么find(T.comp,T.comp+ncols,S.comp[col])==T.comp+ncols证明一定是独立的
        连通分量。

        **/
           if(find(T.comp,T.comp+ncols,S.comp[col])==T.comp+ncols){
            //S.col这个位置的连通分量在T中不复存在,连通分量++
            //此时后面的方格必须涂另外一种颜色
             if(row<nrows-2||  S.color_ncomps[color^1]>1 )  continue;
                //如果还有两行,无论后面怎样填一定不满足题意要求,即不出现2*2的同色方块。
                /**||后面的最难理解:如果已经执行到这里来了,证明新加入的一格和即将推出轮廓线的一格
                颜色不同,而且退出了一个连通分量,那么剩下的格子,必须颜色完全一样。
                这只需要S的轮廓线中的其它格子全部是新加入格子的颜色即可。
                S.color_ncomps[color^1]>1证明S中color^1的连通分量只有将要退出的那一处。即可满足
                **/
                ret+=DP(row,col+1,T,color);
             continue;
           }

        }
       ret+=DP(row,col+1,T,force_color);


    }
    if(force_color<0)  dp[row][col][S.up_left][key]=ret;
    return ret;


}
int main()
{
    int T;scanf("%d",&T);
    while(T--){

        scanf("%d%d",&nrows,&ncols);

        for0(i,nrows)  for0(j,ncols)  {scanf(" %c",&a[i][j]);dp[i][j][0].clear();
        dp[i][j][1].clear();
        }


        solve=false;
        State S;
        memset(&S,0,sizeof S);


        printf("%d\n",DP(0,0,S,-1));


        if(solve) for0(i,nrows)  {
            for0(j,ncols)  putchar(ans[i][j]);
            putchar('\n');
        }
        putchar('\n');
    }

   return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值