P1074靶形数独

Posted wo-bu-yao-bao-ling

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P1074靶形数独相关的知识,希望对你有一定的参考价值。

这道题是一道dfs好题,很多人在题解里用了各种剪枝,以及一些奇奇怪怪的优化,还有大佬用的舞蹈链算法,蒟蒻不会舞蹈链,剪枝的效果也不是很好,只能用一些优化来过这道题了。

这道题虽然已经有很多题解了,但是我还是来交题解的原因是我觉得我的代码跑的,可以给大家提供一些关于搜索顺序的思路,~~希望管理员不要认为我这是重复的解法~~。不过我敢保证,我的代码跑的一定比除了打表题解以外的题解跑的快。

 

~~说实话,这题普通剪枝也可以水过~~,但是既然我们要追求速度,就不能仅仅局限于最优性、可行性剪枝。画了几张图以后发现一个规律,先搜索约束个数多的点,效率最高。这里的约束个数,就是这个点所在行、列、宫已经填好的数的个数之和。(具体内容见下图)

所以我们就得到了一个思路,与处理好搜索顺序,也就是统计每个点的约束个数,先找到最多的,然后记录下来,假设它已经填好(把他所在的行、列、宫的约束个数加一),然后继续找第二多的就好了。最后按照这个顺序在每次dfs结束后找到下一个填数的点,就ok了,这样可以达到总效率888ms,已经非常快了。

![大家可以看一下,第一个就是预处理后的搜索树,第二个是随便搜,差距一目了然](https://cdn.luogu.com.cn/upload/pic/73082.png)

显然先搜索可能性为2的节点比先搜索可能性为4的搜索树小很多,当可能性扩大到9*9=81时,差距会十分明显,效率自然也就高很多了

很多细节详见代码注释。

因为很多人只是预处理了列,按照列每次处理9个,所以效率没有我这种写法高

#include<iostream>
#include<algorithm>
using namespace std;
int ans[13][13];
int cntc[13],cntr[13],cntb[13];
int belong(int x,int y){//判断x,y属于哪一个宫
	int h,l;
	if(x<=3)h=1;
	else if(x<=6)h=2;
	else h=3;
	if(y<=3)l=1;
	else if(y<=6)l=2;
	else l=3;
	return (h-1)*3+l;
}
pair<int,int> s[100];
int cnt=0;
int ANS=0;
int v[10][10]={{0,0,0,0,0,0,0,0,0,0},{0,6,6,6,6,6,6,6,6,6},{0,6,7,7,7,7,7,7,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,9,10,9,8,7,6,},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,7,7,7,7,7,7,6},{0,6,6,6,6,6,6,6,6,6}};
//记录每一个点的分数
void calc(){
	int tot=0;
	for(int i=1;i<=9;i++)
		for(int j=1;j<=9;j++)
			tot+=ans[i][j]*v[i][j];
	ANS=max(ANS,tot);
}//搜索完成后计算答案的函数
int col[20][20],row[20][20],block[20][20];
void dfs(int x,int y,int now){//简单的搜索,按照我们在main函数中预处理的pair<int,int> s的顺序搜索,这样效率最高
	if(now==cnt+1)calc();
	int b=belong(x,y);
	for(int i=1;i<=9;i++){
		if(!col[x][i]&&!row[y][i]&&!block[b][i]){//判断是不是可行
			col[x][i]=row[y][i]=block[b][i]=1;//标记访问
			ans[x][y]=i;
			dfs(s[now+1].first,s[now+1].second,now+1);
			col[x][i]=row[y][i]=block[b][i]=ans[x][y]=0;//回溯
		}
	}
}
int vis[20][20];
int main(){
	for(int i=1;i<=9;i++)
		for(int j=1;j<=9;j++){
			cin>>ans[i][j];
			if(ans[i][j]!=0)vis[i][j]=1,cntc[i]++,cntr[j]++,cntb[belong(i,j)]++,col[i][ans[i][j]]=1,row[j][ans[i][j]]=1,block[belong(i,j)][ans[i][j]]=1;		
			else cnt++;
		}//这里为预处理每一个点在行、列、宫中的限制个数
	for(int k=1;k<=cnt;k++){
		int Max=0,px,py;
		for(int i=1;i<=9;i++
			for(int j=1;j<=9;j++)
				if(cntc[i]+cntr[j]+cntb[belong(i,j)]>Max&&!vis[i][j])
				Max=cntc[i]+cntr[j]+cntb[belong(i,j)],px=i,py=j;
		s[k]=make_pair(px,py);
      //找到可能性最少的点,并假设他已经填好,继续找下一个   
		cntc[px]++,cntr[py]++,cntb[belong(px,py)]++;
		vis[px][py]=1;
	//	cout<<k<<" "<<px<<" "<<py<<endl;	
	}
	dfs(s[1].first,s[1].second,1);//从第一个开始搜索
	if(ANS!=0)
	cout<<ANS<<endl;
	else cout<<-1<<endl;//输出答案
}

  



以上是关于P1074靶形数独的主要内容,如果未能解决你的问题,请参考以下文章

洛谷—— P1074 靶形数独

P1074 靶形数独

洛谷P1074 靶形数独

P1074 靶形数独题解

luogu P1074 靶形数独

洛谷P1074 靶形数独 [搜索]