洛谷P1074 靶形数独——DFS、优化

题目:https://www.luogu.org/problemnew/show/P1074

思路:

1、可以看作八皇后的升级版,八皇后递归深度是8层,每一层供选择的算符有8个。

本题递归深度为cns层,其中,cns为要填的数字总数,每一层供选择的算符为9个。理解了这一点,写DFS有两种写法。

第一种是按要填入数字的格子DFS,后面的AC代码是这一种写法。

另一种写法是逐行逐列地DFS,结构如下:

void dfs(int row,int col){
    if(row==10){  cal(),return; }
    if(col==10)dfs(row+1,1);//列到头后往下一行
    ...
        dfs(row,col+1);//同一行逐列
    ...
}

2、剪枝:引入三个数组,a[row][k],b[col][k],c[palace][k],分别表示数字k在行、列、宫有没有被用过。

3、优化:

1)从数字0最多的一行开始DFS,这样搜索树的枝可以少许多。因为某一行填入数字x,那么这个数字所在的列、宫就不能再填x,一些情况可以被快速地剪枝掉。根据排列组合知识,如果需要填入数字的个数是4与6,前者比后者的情况要少6!-4!=720-24种。

为此,引入结构体:

struct Node{
	int data,row;/*data表示0的个数,row表示行号*/	
};
Node Zero[10];/*记录每行需要填的0的个数*/

int cmp(const Node &a,const Node &b){
	return a.data<b.data;
}

然后用sort对结构体数组Zero排序。

2)预处理,DFS前将待填格子的坐标、宫号、赋分值存到数组srch[][]里,避免频繁调用函数,没有这一步会被卡掉一个点。

3)其它优化:一是对宫号、赋分值打表;二是用getchar读入;三是用inline。但下面的AC代码并没有用这些技巧。

AC代码:

/*预处理时存入每个非0点的坐标、宫号、对应赋分值,
否则会被卡掉一个点
——调取函数是非常耗时的*/ 
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int sudoku[10][10];/*数独sudoku*/ 
int maxx=-1;/*无解输出-1*/ 
bool a[10][10],b[10][10],c[10][10];/*标记行、列、宫是否占领*/ 
int cns=0;/*待填入的数字个数*/ 
int srch[100][4];/*行号、列号、宫号、赋分值*/ 

struct Node{
	int data,row;/*data表示0的个数,row表示行号*/	
};
Node Zero[10];/*记录每行需要填的0的个数*/

int cmp(const Node &a,const Node &b){
	return a.data<b.data;
}

int scr(int i,int j){/*计算点(i,j)的赋分值*/ 
	return abs(i-5)>abs(j-5)?10-abs(i-5):10-abs(j-5);
}

int plc(int i,int j){/*计算点(i,j)所处宫的序号*/ 
	int x=(i-1)/3,y=(j-1)/3+1;
	return x*3+y;
}

void dfs(int N,int sum){
	if(N==cns+1){
		if(maxx<sum)maxx=sum;
		return;
	}
	int row=srch[N][0],col=srch[N][1],
		palace=srch[N][2],score=srch[N][3];
	for(int i=1;i<=9;i++){
		if(!a[row][i] && !b[col][i] && !c[palace][i]){
			a[row][i]=1;b[col][i]=1;c[palace][i]=1;
			//sudoku[row][col]=i;/*点(row,col)填入数字i*/ 
			dfs(N+1,sum+i*score);
			a[row][i]=0;b[col][i]=0;c[palace][i]=0;
			//sudoku[row][col]=0;	
		}
	}				
	return;
}

int main(){
	int sum=0;
	for(int i=1;i<=9;i++){
		Zero[i].row=i; 
		for(int j=1;j<=9;j++){
			cin>>sudoku[i][j];
			int X=sudoku[i][j];
			if(!X)Zero[i].data++;
			else{
				int palace=plc(i,j);
				a[i][X]=1;b[j][X]=1;c[palace][X]=1;
				sum+=X*scr(i,j);
			}
		}	
	}
	
	sort(Zero+1,Zero+10,cmp);/*将行号按0的个数从少到多排序*/ 	
	
	for(int No=1;No<=9;No++){
		int row=Zero[No].row;
		for(int col=1;col<=9;col++)
			if(!sudoku[row][col]){
				srch[++cns][0]=row;/*cns统计要填写的数字个数*/ 
				srch[cns][1]=col;
				srch[cns][2]=plc(row,col);
				srch[cns][3]=scr(row,col);
			}
	}	

	dfs(1,sum);
	cout<<maxx<<endl;
	
	return 0;	 
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值