POJ3074 Sudoku(数独(二))(位运算优化及DFS)

题目链接

(AC代码在最后面)

Description

In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For example,

.2738..1.
.1...6735
.......29
3.5692.8.
.........
.6.1745.3
64.......
9518...7.
.8..6534.

Given some of the numbers in the grid, your goal is to determine the remaining numbers such that the numbers 1 through 9 appear exactly once in (1) each of nine 3 × 3 subgrids, (2) each of the nine rows, and (3) each of the nine columns.

Input

The input test file will contain multiple cases. Each test case consists of a single line containing 81 characters, which represent the 81 squares of the Sudoku grid, given one row at a time. Each character is either a digit (from 1 to 9) or a period (used to indicate an unfilled square). You may assume that each puzzle in the input will have exactly one solution. The end-of-file is denoted by a single line containing the word “end”.

Output

For each test case, print a line representing the completed Sudoku puzzle.

Sample Input

.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end

Sample Output

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936

如果按照POJ2676 Sudoku、POJ2918 Tudoku(数独(一))做是行不通的,需要进一步优化

超时代码:

//CSDN博客:https://blog.csdn.net/qq_40889820
#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define e 2.71828182
#define Pi 3.141592654
using namespace std;
char table[10][10];
int row[10][10],col[10][10],grid[10][10];
bool flag=false;
void dfs(int x,int y)//填第(x,y)格 
{
	if(flag) return;//已找到一种解法 
	if(y>9) y=1,x+=1;
	if(x>9)
	{
		for(int i=1;i<=9;i++)
		cout<<table[i]+1;
		flag=true;
		return;
	}
	if(table[x][y]!='0') dfs(x,y+1);
	else if(table[x][y]=='0') 
	{
		int ord=(x-1)/3*3+(y-1)/3+1;//第几个方块 
		for(int i=1;i<=9;i++)
		{
			if(!row[x][i]&&!col[y][i]&&!grid[ord][i])
			{
				table[x][y]=i+'0';
				row[x][i]=1;col[y][i]=1;grid[ord][i]=1;
				dfs(x,y+1);
				//回溯 
				table[x][y]='0';
				row[x][i]=0;col[y][i]=0;grid[ord][i]=0;
			}
		}
	}
}
int main()
{
	string str; 
	while(cin>>str)
	{
		if(str=="end") break;
		int count=0;
		mem(row,0);mem(col,0);mem(grid,0);
		for(int i=1;i<=9;i++)
			for(int j=1;j<=9;j++)
			{
				table[i][j]=str[count++];
				if(table[i][j]=='.') table[i][j]='0';
				row[i][table[i][j]-'0']=1;
				col[j][table[i][j]-'0']=1;
				int ord=(i-1)/3*3+(j-1)/3+1;
				grid[ord][table[i][j]-'0']=1;
			}
				
		dfs(1,1);
		cout<<endl;
		flag=false;
	}
}

样例用时:

位运算优化,利用三个一维数组row[10],col[10],grid[10]的二进制来记录数字的占用情况。
可填的数字为1~9,故令init=111111111(2)=511=(1<<9)-1,表示一开始1~9都可以填(都未被占用),row[i]表示第i行数字的占用情况,col数组和grid数组同理。当需要获取一个x行y列z方块的空白格子能填的数字只需要先将row[x]&col[y]&grid[z](位与),再利用lowbit函数和预先处理好的figure数组依次取出能填的数字,如100100010即代表数字2、6、9可填。利用异或运算更新状态。

//CSDN博客:https://blog.csdn.net/qq_40889820
#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define e 2.71828182
#define Pi 3.141592654
using namespace std;
char table[10][10];
int row[10],col[10],grid[10];//二进制记录行、列、方块的数字占用情况 
int figure[(1<<8)+1];//
bool flag=false;
int lowbit(int x)
{
	return x&(-x);
}
void dfs(int x,int y)//填第(x,y)格 
{
	if(flag) return;//已找到一种解法 
	if(y>9) y=1,x+=1;
	if(x>9)
	{
		for(int i=1;i<=9;i++)
		cout<<table[i]+1;
		flag=true;
		return;
	}
	if(table[x][y]!='0') dfs(x,y+1);
	else if(table[x][y]=='0') 
	{
		int ord=(x-1)/3*3+(y-1)/3+1;//第几个方块 
		int status=row[x]&col[y]&grid[ord];//statue的二进制记录了行、列、方块数字的占用情况 
		
		for(int i=status;i>0;i-=lowbit(i))
		{
			int num=figure[lowbit(i)];//可填的数字 
			table[x][y]=num+'0';
			row[x] ^= lowbit(i);
			col[y] ^= lowbit(i);
			grid[ord] ^= lowbit(i);
			dfs(x,y+1);
			//回溯
			table[x][y]='0';
			row[x] ^= lowbit(i);
			col[y] ^= lowbit(i);
			grid[ord] ^= lowbit(i); 
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	string str; 
	for(int i=0;i<9;i++) 
		figure[1<<i]=i+1;
	int init=(1<<9)-1;//二进制为111111111 
	while(cin>>str)
	{
		if(str=="end") break;
		int count=0;
		for(int i=1;i<=9;i++)
			row[i]=col[i]=grid[i]=init;	
		for(int i=1;i<=9;i++)
			for(int j=1;j<=9;j++)
			{
				table[i][j]=str[count++];
				if(table[i][j]=='.')
				{
					 table[i][j]='0';
					 continue;
				}
				row[i] ^= (1<<(table[i][j]-'0'-1));
				col[j] ^= (1<<(table[i][j]-'0'-1));
				int ord=(i-1)/3*3+(j-1)/3+1;
				grid[ord] ^= (1<<(table[i][j]-'0'-1));
			}
			/*clock_t t;
		t=clock();*/
		dfs(1,1);
		/*t=clock()-t;
		cout<<"时间为:"<<(((float)t)/CLOCKS_PER_SEC*1000)<<"ms\n";*/
		cout<<endl;
		flag=false;
	}
}

样例用时:(由样例来看优化并不是特别明显?但是实际上位运算优化能提升较大的性能)

优化一下搜索顺序,优先处理可选数字较少的格子,一开始是静态的保存好,并未考虑到一个格子填好胡会对后续格子造成影响。

//CSDN博客:https://blog.csdn.net/qq_40889820
#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define e 2.71828182
#define Pi 3.141592654
using namespace std;
struct node
{
	int x,y,z;//第x行,第y列,第z个方块 
	vector<int> able;//可填数字 
	friend bool operator < (node a,node b)
	{
	    if(a.able.size()!=b.able.size())    return a.able.size()<b.able.size();
	    else if(a.x!=b.x) return a.x<b.x;
	    else return a.y<b.y;
	    /*if(a.x!=b.x) return a.x<b.x;
	    else return a.y<b.y;*/
	}
}p[82];
char table[10][10];
int row[10][10],col[10][10],grid[10][10];
bool flag=false;
void dfs(int step,int sum)//填排好序的第step个空格,共有sum个空格 
{
	if(flag) return;//已找到一种解法 
	if(step>sum)
	{
		for(int i=1;i<=9;i++)
		cout<<table[i]+1;
		flag=true;
		return;
	}
	for(int j=0;j<p[step].able.size()&&!flag;j++)
	{
		int i=p[step].able[j];
		if(!row[p[step].x][i]&&!col[p[step].y][i]&&!grid[p[step].z][i])
		{
			table[p[step].x][p[step].y]=i+'0';
			row[p[step].x][i]=1;col[p[step].y][i]=1;grid[p[step].z][i]=1;
			dfs(step+1,sum);
			//回溯 
			table[p[step].x][p[step].y]='0';
			row[p[step].x][i]=0;col[p[step].y][i]=0;grid[p[step].z][i]=0;
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	string str; 
	while(cin>>str)
	{
		if(str=="end") break;
		int count=0;
		mem(row,0);mem(col,0);mem(grid,0);
		for(int i=1;i<=9;i++)
			for(int j=1;j<=9;j++)
			{
				table[i][j]=str[count++];
				if(table[i][j]=='.') table[i][j]='0';
				row[i][table[i][j]-'0']=1;
				col[j][table[i][j]-'0']=1;
				int ord=(i-1)/3*3+(j-1)/3+1;
				grid[ord][table[i][j]-'0']=1;
			}
		
		count=0; 
		for(int i=1;i<=9;i++)
			for(int j=1;j<=9;j++)
			{
				if(table[i][j]!='0') continue;
				count++;
				p[count].able.clear();//血与泪的教训!!这个坑太深了 
				p[count].x=i,p[count].y=j;
				int ord=(i-1)/3*3+(j-1)/3+1;
				p[count].z=ord;
				for(int k=1;k<=9;k++)
				if(!row[i][k]&&!col[j][k]&&!grid[ord][k])
				p[count].able.push_back(k);
			}	

		sort(p+1,p+count+1);
		/*clock_t t;
		t=clock();*/
		dfs(1,count);
		/*t=clock()-t;
		cout<<"时间为:"<<(((float)t)/CLOCKS_PER_SEC*1000)<<"ms\n";*/
		cout<<endl;
		flag=false; 
	}
}

当对node里<号的重载为

if(a.able.size()!=b.able.size())    return a.able.size()<b.able.size();
else if(a.x!=b.x) return a.x<b.x;
else return a.y<b.y;

时,即按可选数字数递增,若相同则按原顺序进行搜索,所需时间为:

当对node里<号的重载为

if(a.x!=b.x) return a.x<b.x;
else return a.y<b.y;

时,即按原顺序(数独中从上到下,从左到右)进行搜索,所需时间为:

很显然,这样做并不能满足要求。

后来改为动态查询,每次都查找可选数字最少的格子。但还是不幸超时

//CSDN博客:https://blog.csdn.net/qq_40889820
#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define e 2.71828182
#define Pi 3.141592654
#define P pair<int,int>
using namespace std;
char table[10][10];
int row[10],col[10],grid[10];//二进制记录行、列、方块的数字占用情况 
int figure[(1<<8)+1];//
int lowbit(int x)
{
	return x&(-x);
}
int z[10][10]={{0,0,0,0,0,0,0,0,0,0},
			   {0,1,1,1,2,2,2,3,3,3},
			   {0,1,1,1,2,2,2,3,3,3},
			   {0,1,1,1,2,2,2,3,3,3},
			   {0,4,4,4,5,5,5,6,6,6},
			   {0,4,4,4,5,5,5,6,6,6},
			   {0,4,4,4,5,5,5,6,6,6},
			   {0,7,7,7,8,8,8,9,9,9},
			   {0,7,7,7,8,8,8,9,9,9},
			   {0,7,7,7,8,8,8,9,9,9}};
int num(int n)//返回可填数字个数 
{
	int ans=0;
	for(int i=n;i;i-=lowbit(i))
	ans++;
	return ans;
}
P Min()//获得可填数字数最少的格子 
{
	int x=0,y=0,Min=1<<30;
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(table[i][j]!='0') continue;
			int status=row[i]&col[j]&grid[z[i][j]];
			if(!status) return P(-1,-1);//该格子无可填的数字 
			if(num(status)<Min) Min=num(status),x=i,y=j;
		}	
	}
	return P(x,y); 
}
bool dfs(int x,int y)//填第(x,y)格 
{
	int status=row[x]&col[y]&grid[z[x][y]];//statues的二进制记录了行、列、方块数字的占用情况
	for(int i=status;i>0;i-=lowbit(i))
	{
		int num=figure[lowbit(i)];//可填的数字 
		table[x][y]=num+'0';
		row[x] ^= lowbit(i);
		col[y] ^= lowbit(i);
		grid[z[x][y]] ^= lowbit(i);
		P p=Min();
		if(p.first==-1)
		{
			table[x][y]='0';
			row[x] ^= lowbit(i);
			col[y] ^= lowbit(i);
			grid[z[x][y]] ^= lowbit(i); 
			continue;
		}
		if(p.first==0) return true;
		if(dfs(p.first,p.second)) return true;
		table[x][y]='0';
		row[x] ^= lowbit(i);
		col[y] ^= lowbit(i);
		grid[z[x][y]] ^= lowbit(i); 
	} 
	return false;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	string str; 
	for(int i=0;i<9;i++) 
		figure[1<<i]=i+1;
	int init=(1<<9)-1;//二进制为111111111 
	while(cin>>str)
	{
		if(str=="end") break;
		int count=0;
		for(int i=1;i<=9;i++)
			row[i]=col[i]=grid[i]=init;	
		for(int i=1;i<=9;i++)
			for(int j=1;j<=9;j++)
			{
				table[i][j]=str[count++];
				if(table[i][j]=='.')
				{
					 table[i][j]='0';
					 continue;
				}
				row[i] ^= (1<<(table[i][j]-'0'-1));
				col[j] ^= (1<<(table[i][j]-'0'-1));
				grid[z[i][j]] ^= (1<<(table[i][j]-'0'-1));
				
			}
		/*clock_t t;
		t=clock();*/
		P p=Min();
		dfs(p.first,p.second);
		//t=clock()-t;
		for(int i=1;i<=9;i++)
		cout<<table[i]+1;
		//cout<<"时间为:"<<(((float)t)/CLOCKS_PER_SEC*1000)<<"ms\n";
		cout<<endl;		
	}
}

然后将各个10进制数字代表能填数字的个数保存在数组里,终于是过了

显然还有更进一步的优化,留在POJ3076去探索吧。

AC代码:

//CSDN博客:https://blog.csdn.net/qq_40889820
#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define e 2.71828182
#define Pi 3.141592654
#define P pair<int,int>
using namespace std;
char table[10][10];
int row[10],col[10],grid[10];//二进制记录行、列、方块的数字占用情况 
int num[(1<<9)];//
int lowbit(int x)
{
	return x&(-x);
}
int z[10][10]={{0,0,0,0,0,0,0,0,0,0},
			   {0,1,1,1,2,2,2,3,3,3},
			   {0,1,1,1,2,2,2,3,3,3},
			   {0,1,1,1,2,2,2,3,3,3},
			   {0,4,4,4,5,5,5,6,6,6},
			   {0,4,4,4,5,5,5,6,6,6},
			   {0,4,4,4,5,5,5,6,6,6},
			   {0,7,7,7,8,8,8,9,9,9},
			   {0,7,7,7,8,8,8,9,9,9},
			   {0,7,7,7,8,8,8,9,9,9}};
int fun(int n)//返回可填数字个数 
{
	int ans=0;
	for(int i=n;i;i-=lowbit(i))
	ans++;
	return ans;
}
P Min()//获得可填数字数最少的格子 
{
	int x=0,y=0,Min=1<<30;
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(table[i][j]!='0') continue;
			int status=row[i]&col[j]&grid[z[i][j]];
			if(!status) return P(-1,-1);//该格子无可填的数字 
			if(num[status]<Min) Min=num[status],x=i,y=j;
		}	
	}
	return P(x,y); 
}
bool dfs(int x,int y)//填第(x,y)格 
{
	int status=row[x]&col[y]&grid[z[x][y]];//statues的二进制记录了行、列、方块数字的占用情况
	for(int i=1;i<=9;i++)
	{
		if(status>>(i-1) & 1)//数字i未被占用
		{
			table[x][y]=i+'0';
			row[x] ^= (1<< i-1);
			col[y] ^= (1<< i-1);
			grid[z[x][y]] ^= (1<< i-1);
			P p=Min();
			if(p.first==-1)
			{
				table[x][y]='0';
				row[x] ^= (1<< i-1);
				col[y] ^= (1<< i-1);
				grid[z[x][y]] ^= (1<< i-1); 
				continue;
			}
			if(p.first==0) return true;
			if(dfs(p.first,p.second)) return true;
			table[x][y]='0';
			row[x] ^= (1<< i-1);
			col[y] ^= (1<< i-1);
			grid[z[x][y]] ^= (1<< i-1); 	
		} 
	}
	return false;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	string str; 
	for(int i=0;i<(1<<9);i++) 
		num[i]=fun(i);
	int init=(1<<9)-1;//二进制为111111111 
	while(cin>>str)
	{
		if(str=="end") break;
		int count=0;
		for(int i=1;i<=9;i++)
			row[i]=col[i]=grid[i]=init;	
		for(int i=1;i<=9;i++)
			for(int j=1;j<=9;j++)
			{
				table[i][j]=str[count++];
				if(table[i][j]=='.')
				{
					 table[i][j]='0';
					 continue;
				}
				row[i] ^= (1<<(table[i][j]-'0'-1));
				col[j] ^= (1<<(table[i][j]-'0'-1));
				grid[z[i][j]] ^= (1<<(table[i][j]-'0'-1));
			}
		/*clock_t t;
		t=clock();*/
		P p=Min();
		dfs(p.first,p.second);
		//t=clock()-t;
		for(int i=1;i<=9;i++)
		cout<<table[i]+1;
		//cout<<"时间为:"<<(((float)t)/CLOCKS_PER_SEC*1000)<<"ms\n";
		cout<<endl;
		
	}
}

样例用时:(显然样例只能当样例看)

参考资料:
搜索_常规DFS_POJ3074_Sudoku
Sicily1317-Sudoku-位运算暴搜
二进制-高效位运算

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值