《算法竞赛进阶指南》数独2

数独2

请你将一个16x16的数独填写完整,使得每行、每列、每个4x4十六宫格内字母A~P均恰好出现一次。

保证每个输入只有唯一解决方案。

数独2.jpg

输入格式
输入包含多组测试用例。

每组测试用例包括16行,每行一组字符串,共16个字符串。

第i个字符串表示数独的第i行。

字符串包含字符可能为字母A~P或”-“(表示等待填充)。

测试用例之间用单个空行分隔,输入至文件结尾处终止。

输出格式
对于每个测试用例,均要求保持与输入相同的格式,将填充完成后的数独输出。

每个测试用例输出结束后,输出一个空行。

输入样例:
–A----C-----O-I
-J–A-B-P-CGF-H-
–D--F-I-E----P-
-G-EL-H----M-J–
----E----C–G—
-I–K-GA-B—E-J
D-GP–J-F----A–
-E—C-B–DP–O-
E–F-M–D--L-K-A
-C--------O-I-L-
H-P-C–F-A–B—
—G-OD—J----H
K—J----H-A-P-L
–B--P–E--K–A-
-H–B--K–FI-C–
–F—C–D--H-N-
输出样例:
FPAHMJECNLBDKOGI
OJMIANBDPKCGFLHE
LNDKGFOIJEAHMBPC
BGCELKHPOFIMAJDN
MFHBELPOACKJGNID
CILNKDGAHBMOPEFJ
DOGPIHJMFNLECAKB
JEKAFCNBGIDPLHOM
EBOFPMIJDGHLNKCA
NCJDHBAEKMOFIGLP
HMPLCGKFIAENBDJO
AKIGNODLBPJCEFMH
KDEMJIFNCHGAOPBL
GLBCDPMHEONKJIAF
PHNOBALKMJFIDCEG
IAFJOECGLDPBHMNK

本题设计到的算法是dfs,重点在于剪枝我们首先借鉴数独1的思想,每次搜索先遍历可能次数小的位置,初次之外我们还必须进行其他剪枝:

每行、每列、每个16*16宫格都进行剪枝,每次遍历之后若判断当前出现不合法的填写方式,则回溯,若找到某空格有唯一填的形式,则直接进行下一层搜索。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=16;
int state[N][N];//用二进制数标记每一个位置的需要填字母的情况
char str[N][N+1];
int ones[1<<N],map[1<<N];//用于二进制中的识别问题
char bstr[N*N+1][N][N+1];//每一种空格数对应一个16*16字符宫格
int bstate[N*N+1][N][N],bstate2[N*N+1][N][N];
//每一种空格数对应一种标记状态
inline int lowbit(int k)
{
	return k& -k;
}
void draw(int x,int y,int c)
{
	str[x][y]='A'+c;
	for(int i=0;i<N;i++)
	{//对(x,y)向对应的行、列进行标记
		state[x][i]&=~(1<<c);
		state[i][y]&=~(1<<c);
	}//对(x,y)的九宫格进行标记   
	int sx=x/4*4,sy=y/4*4;
	for(int i=0;i<4;i++)
		for(int j=0;j<4;j++)
			state[sx+i][sy+j]&=~(1<<c);
	state[x][y]=1<<c;//将(x,y)该位置标记为只能填该字母	
}
bool dfs(int cnt)
{
	if(!cnt)return true;//如果当前空格数为0表示所有的空格都填上了
    int kcnt=cnt;//对当前所有的状态都进行保存,在下面剪枝的过程中若判断出当前状况非法,则还原会当前步骤的初始状态,然后回溯
    memcpy(bstate[kcnt],state,sizeof state);
    memcpy(bstr[kcnt],str,sizeof str);

    for(int i=0;i<N;i++)
    	for(int j=0;j<N;j++)
    		if(str[i][j]=='-')
    		{
    			if(!state[i][j])
    			{
    				memcpy(state,bstate[kcnt],sizeof state);
    				memcpy(str,bstr[kcnt],sizeof str);
    				return false;
    			}
    			if(ones[state[i][j]]==1)
    			{
    				draw(i,j,map[state[i][j]]);
    				cnt--;
    			}
    		}

    //对每一行进行判断,剪枝
	for(int i=0;i<N;i++)
	{
        int sor=0,sand=(1<<N)-1;
        int drawn=0;
        //每一行初始化sor表示当前行每一个位置能填的字母的并集
        //sand表示的是该行中所有的这样的字母:该字母是具有放到某一位置的唯一性
	    //drawn表示的是该行所有已经填上了的字母
	    for(int j=0;j<N;j++)
	    {
	    	int s=state[i][j];
	    	sand&=~(sor&s);//当前位置的要填情况与前面所有的要填的所有情况的重复情况
	    	//都在sand中标0去除掉
	    	sor|=s;//sor表示前j个位置所有的可能的要填的情况
	        if(str[i][j]!='-')
	        	drawn|=state[i][j];//drawn标记所有的已经填过了的情况
	    }

	    if(sor!=(1<<N)-1)
	    {//由于sor标记的是当前行所有的可能填的字母,如果合法的化必定是有16种字母填入到该行中的
	    //若不是的化就表示非法,进行剪枝	
         memcpy(state,bstate[kcnt],sizeof state);
         memcpy(str,bstr[kcnt],sizeof str);
         return false;
	    }
	    for(int j=sand;j;j-=lowbit(j))
	    {//由于sand中存储的是该行所有的确定性的字母
	     //我们接下来把这些确定性的字母且是在空格要填的确定性的字母填入到空格中	
	    	int t=lowbit(j);
	    	if(!(drawn&t))
	    	{//当该行中所有已经填入的字母中不包括当前确定性的字母
               for(int k=0;k<N;k++)
               	if(state[i][k]&t)
               	{//当i行k列正好需要匹配的是当前字母时
               		draw(i,k,map[t]);
                    cnt--;
                    break;
               	}
	    	}
	    }
	}


	//下面对列进行剪枝优化,过程与行的相同
	for(int i=0;i<N;i++)
	{
		int sor=0,sand=(1<<N)-1;
		int drawn=0;
		for(int j=0;j<N;j++)
		{
			int s=state[j][i];
			sand&=~(sor&s);
			sor|=s;

			if(str[j][i]!='-')
				drawn|=state[j][i];
		}
		if(sor!=(1<<N)-1)
		{
			memcpy(state,bstate[kcnt],sizeof state);
			memcpy(str,bstr[kcnt],sizeof str);
			return false;
		}
		for(int j=sand;j;j-=lowbit(j))
		{
			int t=lowbit(j);
			if(!(drawn&t))
			{
				for(int k=0;k<N;k++)
					if(state[k][i]&t)
					{
						draw(k,i,map[t]);
						cnt--;
						break;
					}
			}
		}
	}



	//下面对每一个16宫格进行剪枝,原理与上面相同
	for(int i=0;i<N;i++)
	{//i遍历的16个宫格
		int sor=0,sand=(1<<N)-1;
		int drawn=0;
		for(int j=0;j<N;j++)
		{//j遍历的时宫格里面的16个位置 
            int sx=i/4*4,dx=j/4;
            int sy=i%4*4,dy=j%4;
            //其中(sx,sy)时每一个宫格的左上角,dx、dy时用来遍历这个宫格的所有位置
            
            int s=state[sx+dx][sy+dy];
            sand&=~(sor&s);
            sor|=s;

            if(str[sx+dx][sy+dy]!='-')
            	drawn|=state[sx+dx][sy+dy];
		}

		if(sor!=(1<<N)-1)
		{
			memcpy(state,bstate[kcnt],sizeof state);
			memcpy(str,bstr[kcnt],sizeof str);
			return false;
		}
		for(int j=sand;j;j-=lowbit(j))
		{
			int t=lowbit(j);
			if(!(drawn&t))
			{
				for(int k=0;k<N;k++)
				{
				int sx=i/4*4,dx=k/4;
				int sy=i%4*4,dy=k%4;
				if(state[sx+dx][sy+dy]&t)
				{
					draw(sx+dx,sy+dy,map[t]);
					cnt--;
					break;
				}
				}
				
			}
		}
	}
      

    if(!cnt)return true;
    int x,y,s=100;
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
        if(str[i][j]=='-'&&ones[state[i][j]]<s)
        {
        	x=i,y=j;
        	s=ones[state[i][j]];
        }      
    memcpy(bstate2[kcnt],state,sizeof state);
    
    for(int i=state[x][y];i;i-=lowbit(i))
    {
    	memcpy(state,bstate2[kcnt],sizeof state);
    	draw(x,y,map[lowbit(i)]);
    	if(dfs(cnt-1))return true;
    } 
    memcpy(state,bstate[kcnt],sizeof state);
    memcpy(str,bstr[kcnt],sizeof str);
    return false;   
}
int main()
{
	//初始化两个二进制计算的数组
    for(int i=0;i<N;i++)
    	map[1<<i]=i;
    for(int i=1;i<1<<N;i++)
    {
    	for(int j=i;j;j-=lowbit(j))
    		ones[i]++;
    }
    while(cin>>str[0])
    {
    	for(int i=1;i<N;i++)
    		cin>>str[i];
    	for(int i=0;i<N;i++)
    		for(int j=0;j<N;j++)
    			state[i][j]=(1<<N)-1;//初始化每一个标记位,表示每个位置可以填任何数
        int cnt=0;
        for(int i=0;i<N;i++)
        	for(int j=0;j<N;j++)
        		if(str[i][j]!='-')
        		{
                   draw(i,j,str[i][j]-'A');
        		}
                 else cnt++;
       dfs(cnt);

       for(int i=0;i<N;i++)
       	cout<<str[i]<<endl;
       cout<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值