每日三题-Day6-C(CSU 1508 地图的四着色 巧妙剪枝搜索dfs)

1508: 地图的四着色

            Time Limit: 20 Sec       Memory Limit: 128 Mb       Submitted: 222       Solved: 102    

Description

有一个R行C列的网格地图,每个国家是一个四连通区域。你的任务是用红,绿,蓝,黄四种颜色给地图着色,使得相邻国家的颜色不同。
一个人着色比较无趣,所以你想请女朋友陪你一起涂——你涂红绿,她涂蓝黄。当然,绅士是不会让让女朋友受累的,所以她最多只需涂5个国家(恰好5个也行)。
你的任务是统计有多少种着色的方法。注意,每个颜色都至少要用一次。

Input

输入包含不超过100组数据。每组数据第一行为两个整数R和C (1<=R,C<=20),即网格的行数和列数。以下R行每行C个大写字母。相同字母所组成的四连通区域代表一个国家。输入保证国家数目不超过30,并且大多数测试点的国家数都比较小。

Output

对于每组数据,输出测试点编号和着色方案数。

Sample Input

2 4
AABB
BBAA
1 5
ABABA
4 7
AABAABB
ABBCCCB
BBAACBB
CCABBAC

Sample Output

Case 1: 24
Case 2: 144
Case 3: 3776

思路:

先用dfs,将四连通的相同字母的每一片编号,比如
AABB
BBAA
处理为
0011
2233
然后将每一片编号相同的区域作为一个以这个号码为编号的结点,构建邻接矩阵。
如果一个编号的四连通有其他编号,说明这两个结点连通,邻接。
得到邻接矩阵:
0110
1001
1001
0110
然后用搜索,尝试每一个结点使用每一种颜色(用邻接矩阵判断方案是否可行)

直接搜索会超时,我们尝试剪枝:
男使用AB颜色,女使用CD颜色。
如果我们强制规定,男使用的第一种颜色是A,女使用的第一种颜色是C,那么我们可以得到很大的时间上的优化。
而先使用哪个颜色,都是一样的。所以只需要在这基础上得到的结果乘以4就是最终答案。

AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;
char maps[22][22];
int num[22][22];
int adjMa[33][33];
int color[33];
int ax[5]= {0,0,1,-1};
int ay[5]= {1,-1,0,0};
int r,c;
int n;
void dfs(int a,int b,int p)
{
    num[a][b]=p;
    for(int i=0; i<4; i++)
    {
        int x=a+ax[i];
        int y=b+ay[i];
        if(x>=0&&x<r&&y>=0&&y<c&&maps[x][y]==maps[a][b]&&num[x][y]==-1)
        {
            dfs(x,y,p);
        }
    }
}
int check(int step,int a)
{
    for(int i=0;i<n;i++)
    {
        if(adjMa[step][i]==1&&color[i]==a) return 0;
    }
    return 1;
}
int dfsAns(int step,int A,int B,int C,int D)
{
    int ans=0;
    if(step==n) return ans+=A&&B&&C&&D;
    if(check(step,0))
    {
        color[step]=0;
        ans+=dfsAns(step+1,A+1,B,C,D);
    }
    if(A&&check(step,1))
    {
        color[step]=1;
        ans+=dfsAns(step+1,A,B+1,C,D);
    }
    if(C+D<5&&check(step,2))
    {
        color[step]=2;
        ans+=dfsAns(step+1,A,B,C+1,D);
    }
    if(C&&C+D<5&&check(step,3))
    {
        color[step]=3;
        ans+=dfsAns(step+1,A,B,C,D+1);
    }
    color[step]=-1;
    return ans;
}

int main()
{
    int cake=1;
    while(~scanf("%d%d",&r,&c))
    {
        getchar();
        for(int i=0; i<r; i++)
        {
            for(int j=0; j<c; j++)
            {
                scanf("%c",&maps[i][j]);
            }
            getchar();
        }
        memset(num,-1,sizeof(num));
        int p=0;
        for(int i=0; i<r; i++)
        {
            for(int j=0; j<c; j++)
            {
                if(num[i][j]==-1)
                {
                    dfs(i,j,p);
                    p++;
                }
            }
        }
        n=p;
        memset(adjMa,0,sizeof(adjMa));
        for(int i=0; i<r; i++)
        {
            for(int j=0; j<c; j++)
            {
                for(int k=0; k<4; k++)
                {
                    int x=i+ax[k];
                    int y=j+ay[k];
                    if(x>=0&&x<r&&y>=0&&y<c&&num[x][y]!=num[i][j])
                    {
                        adjMa[num[i][j]][num[x][y]]=1;
                        adjMa[num[x][y]][num[i][j]]=1;
                    }
                }
            }
        }
        memset(color,-1,sizeof(color));
        int ans=dfsAns(0,0,0,0,0);
        printf("Case %d: %d\n",cake++,ans*4);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值