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,

. 2 7 3 8 . . 1 .
. 1 . . . 6 7 3 5
. . . . . . . 2 9
3 . 5 6 9 2 . 8 .
. . . . . . . . .
. 6 . 1 7 4 5 . 3
6 4 . . . . . . .
9 5 1 8 . . . 7 .
. 8 . . 6 5 3 4 .

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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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<>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<

样例用时:

位运算优化,利用三个一维数组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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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<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<>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<

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

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

//CSDN博客:https://blog.csdn.net/qq_40889820
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 able;//可填数字 
	friend bool operator < (node a,node b)
	{
	    if(a.able.size()!=b.able.size())    return a.able.size()sum)
	{
		for(int i=1;i<=9;i++)
		cout<>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<

当对node里<号的重载为

if(a.able.size()!=b.able.size())    return a.able.size()

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

当对node里<号的重载为

if(a.x!=b.x) return a.x

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

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

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

//CSDN博客:https://blog.csdn.net/qq_40889820
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
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)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<>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<

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

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

AC代码:

//CSDN博客:https://blog.csdn.net/qq_40889820
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
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]>(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<

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

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

你可能感兴趣的:(#,>>>>深度优先搜索,∨∨Acm)