「NOIP2009」靶形数独

1.前言:

点赞 投币 收藏(三连键)

前置知识:

D F S DFS DFS

想必大家都学过 不知道滴 出门右转 百度欢迎您

2.正文

「NOIP2009」靶形数独

题意:

完成数独 计算得分 输出答案

思路:

D F S DFS DFS + 虚拟数据现实化 (打表)

写完程序很简单 但是很容易 T L E TLE TLE

所以就想出了 将得分表 打表出来的简单做法

还有一些做数独的技巧用在里面

我没有用位运算 jiao lian会不会打我

做法:

先开几个数组 (老朋友了)
int a[12][12];
int t[10];
int s[15][15],w[15][15],q[15][15];
int vis[10];
然后手得分

(格式要注意 建议不要掐着开9 开大一点不会爆)

int score[12][12]={
{0,0,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}};

相信大家都玩过数独 没玩过的请看题目

题目规定 同一3宫格内 同一列 同一行不能有重复的数字(1—9)

这也是上面为什么开了这么多数组的原因

S S S 数组用来记录这一列的填数情况

W W W 数组用来记录这一行的填数情况

Q Q Q 数组用来记录这一小九宫格的填数情况

(都是标记数组

然后 还是为了不 T L E TLE TLE 我们要手打小九宫格位置判断函数

(其实也还好)

int where(int x,int y)//符合其性能 找到这个玩意儿 在哪一个小方格 
{
//x是行数 y是列数
	if (x<=3)//在第一个3行里(三行三列为一个宫格 每一个有三行九列 要一个一个判断)
	{
		if (y<=3)
		{
			return 1;
		}
		if (y<=6)
		{
			return 2;
		}
		if (y<=9)
		{
			return 3;
		}
	}
	if (x<=6)//在第二个三行里
	{
		if (y<=3)
		{
			return 4;
		}
		if (y<=6)
		{
			return 5;
		}
		if (y<=9)
		{
			return 6;
		}
	}
	if (x<=9)//在第三个三行里
	{
		if (y<=3)
		{
			return 7;
		}
		if (y<=6)
		{
			return 8;
		}
		if (y<=9)
		{
			return 9;
		}
	}
	return 0;
	// O(3)的复杂度 应该? 
}//光这个就占据了47排 好像可以不要括号 
然后就排序

这个排序是用来不是 v i s vis vis 数组的

v i s vis vis 数组是用来记录每一行有多少个数是填了(初始)的

这个排序的意义是用 v i s vis vis 数组的数据 来影响 T T T 数组的排列 也就是从哪个数入手

本质上的想法是从某一行中填数较多的入手(也是数独的解题技巧之一)
bool cmp(int a,int b)
{
	return vis[a]>vis[b];
}
紧接着是平平无奇的 D F S DFS DFS
代码
void dfs(int tot,int u,int x,int y)//答案以及迭代下标 
{	
	if (y==10)
	{
		if(u==9)
		{
			ans=max(tot,ans);//答案打擂台 
			return;
		}
		dfs(tot,u+1,t[u+1],1);
		return;
	}
	if (x>=10||y>=10)
	{
		return;
	} 
	int there=where(x,y); 
	if (a[x][y]!=0)//填过了 或者 本来就有数 
	{
		dfs(tot+a[x][y]*score[x][y],u,x,y+1);
	}//在这里 前面的 虚拟数据现实化  展现了其作用
	//虚拟数据 现实化 节省了时间复杂度 
	else
	{
		for (int i=1;i<=9;i++)
		{
			if (s[x][i]==0&&w[i][y]==0&&q[there][i]==0)
			{//没填过 本来没有数 在的小方格里也没有重复的 
				s[x][i]=1;
				w[i][y]=1;
				q[there][i]=1;//标记
				dfs(tot+i*score[x][y],u,x,y+1);
				s[x][i]=0;
				w[i][y]=0;
				q[there][i]=0;//朴素版回溯(手动) 
			}
		}	
	}
	return;
}
最后是主函数
int main(){
	for (int i=1;i<=9;i++)
	{
		t[i]=i;
		for (int j=1;j<=9;j++)
		{
			cin>>a[i][j];
			if (a[i][j]!=0)//本来有数 标记一下  
			{
				s[i][a[i][j]]=1;
				w[a[i][j]][j]=1;
				q[where(i,j)][a[i][j]]=1;
				vis[i]++;
			}
		}
	}
	sort(t+1,t+10,cmp);//按照 vis 里的多少 从大到小排
   //从多的入手 更好做亿点
	dfs(0,1,t[1],1);//**的dfs 
	if(ans==0)
	{
		cout<<-1;
		return 0;//其实可以把 $ans$ 的初始值赋值为-1的 看个人喜好
	} 
	cout<<ans;
	//本来还应该再check一下
	//碍于此题只有1s
	//所以check被吃掉啦(绝对不是我不想写) 
	//码数新高
	//从来没有哪一个程序让我写这么多注释
	//太酷啦!!! 
	return 0;
}

A C AC AC代码放在下面

(无坑 放心食用)

//优化了好久
//不要tle
//dfs我恨你 
//玩过数独的 看见这道题 都沉默了 
#include <bits/stdc++.h>
using namespace std;
int a[12][12];
int t[10];
int s[15][15],w[15][15],q[15][15];
int score[12][12]={
{0,0,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}};
//这算打表吗 不 这不算 这算将虚拟数据 现实化 
//这个格式调了我有5分钟? 
int vis[10];
int ans=0;
int where(int x,int y)
{
	if (x<=3)
	{
		if (y<=3)
		{
			return 1;
		}
		if (y<=6)
		{
			return 2;
		}
		if (y<=9)
		{
			return 3;
		}
	}
	if (x<=6)
	{
		if (y<=3)
		{
			return 4;
		}
		if (y<=6)
		{
			return 5;
		}
		if (y<=9)
		{
			return 6;
		}
	}
	if (x<=9)
	{
		if (y<=3)
		{
			return 7;
		}
		if (y<=6)
		{
			return 8;
		}
		if (y<=9)
		{
			return 9;
		}
	}
	return 0;
	//O(3)的复杂度 应该? 
}//光这个就占据了47排 好像可以不要括号 
bool cmp(int a,int b)
{
	return vis[a]>vis[b];
}
//反着排 不过好像有个stl也能反着排?
//反正我记不得了 
void dfs(int tot,int u,int x,int y)//答案以及迭代下标(好高级的样子) 
{	
	if (y==10)
	{
		if(u==9)
		{
			ans=max(tot,ans);//答案打擂台 
			return;
		}
		dfs(tot,u+1,t[u+1],1);
		return;
	}
	if (x>=10||y>=10)
	{
		return;
	} 
	int there=where(x,y);
	if (a[x][y]!=0)//填过了 或者 本来就有数 
	{
		dfs(tot+a[x][y]*score[x][y],u,x,y+1);
	}//在这里 前面的 虚拟数据现实化  展现了其作用
	//虚拟数据 现实化 节省了时间复杂度 
	else
	{
		for (int i=1;i<=9;i++)
		{
			if (s[x][i]==0&&w[i][y]==0&&q[there][i]==0)
			{//没填过 本来没有数 在的小方格里也没有重复的 
				s[x][i]=1;
				w[i][y]=1;
				q[there][i]=1;
				dfs(tot+i*score[x][y],u,x,y+1);
				s[x][i]=0;
				w[i][y]=0;
				q[there][i]=0;//朴素版回溯(手动) 
			}
		}	
	}
	return;
}
int main(){
	for (int i=1;i<=9;i++)
	{
		t[i]=i;
		for (int j=1;j<=9;j++)
		{
			cin>>a[i][j];
			if (a[i][j]!=0)//本来有数 标记一下 
			{
				s[i][a[i][j]]=1;
				w[a[i][j]][j]=1;
				q[where(i,j)][a[i][j]]=1;
				vis[i]++;
			}
		}
	}
	sort(t+1,t+10,cmp);//从大到小排
	dfs(0,1,t[1],1);//**的dfs 
	if(ans==0)
	{
		cout<<-1;
		return 0;
	} 
	cout<<ans;
	//本来还应该再check一下
	//碍于此题只有1s
	//所以check被吃掉啦(绝对不是我不想写 ) 
	//码数新高
	//从来没有哪一个程序让我写这么多注释
	//太酷啦!!! 
	return 0;
}

bitset优化

//更多的 bitset 用法请参见 https://zh.cppreference.com/w/cpp/utility/bitset。
//the code is from chenjh
#include<cstdio>//输入输出头文件。
#include<algorithm>//std::sort 排序以及 std::max 函数。
#include<bitset>//std::bitset。
#include<functional>//std::greater 重载运算符函数。
#include<utility>//std::pair 二元组。
#define G(x,y) ((x/3)*3+y/3)//当前所属的宫格位置。
typedef std::bitset<10> B;
typedef std::pair<int,int> PII;
const int n=9;
B h[n],l[n],g[n];
int a[n][n];
const int s[n][n]= {
	{6,6,6,6,6,6,6,6,6},
	{6,7,7,7,7,7,7,7,6},
	{6,7,8,8,8,8,8,7,6},
	{6,7,8,9,9,9,8,7,6},
	{6,7,8,9,10,9,8,7,6},
	{6,7,8,9,9,9,8,7,6},
	{6,7,8,8,8,8,8,7,6},
	{6,7,7,7,7,7,7,7,6},
	{6,6,6,6,6,6,6,6,6}
};//预处理得分表。
int c[11];//得分为 i 的方格剩余数量为 c[i]。
int ans=0;
bool F=0;//是否有解。
int f() {
	return 9*(c[6]*6+c[7]*7+c[8]*8+c[9]*9+c[10]*10);   //估价函数,但是这个剪枝没啥大用,意思即把剩下的每个方格填上 9 以后的分数。
}
PII H[20];//二元组,first 存当前有多少个值确定,second 存行号。
void dfs(int pos,int y,const int w) {
	int x=H[pos].second;
	if(y>=n) x=H[++pos].second,y=0;
	if(pos>=n || x>=n) {
		F=1,ans=std::max(ans,w);    //更新答案。
		return;
	}
	if(w+f()<=ans) return;//剪枝优化。
	if(a[x][y]) {
		dfs(pos,y+1,w+a[x][y]*s[x][y]);
		return;
	}
	B f=h[x]&l[y]&g[G(x,y)];
	if(f.none()) return;//none 函数的意思是此 bitset 是否每一位都为 false。
	for(int i=f._Find_first(); i<=n; i=f._Find_next(i)) {
		//_Find_first() 和 _Find_next 均为内置函数,前者的意思为找到第一个为 1 的下标,后者的意思为找到第 i 个以后的第一个为 1 的下标,以上函数如果查找失败均返回 std::bitset 的大小(这里即为 10)。
		h[x][i]=l[y][i]=g[G(x,y)][i]=0;//标记当前位置已使用。
		dfs(pos,y+1,w+i*s[x][y]);
		h[x][i]=l[y][i]=g[G(x,y)][i]=1;//消除标记。
	}
}
int main() {
	c[6]=32,c[7]=24,c[8]=16,c[9]=8,c[10]=1;
	for(int i=0; i<n; i++) {
		h[i]=l[i]=g[i]=0x3fe;
		H[i].second=i;
	}//初始化,3fe 的二进制形式即为 1111111110,标记 1 为未使用,0 为已使用。
	for(int i=0; i<n; i++) {
		for(int j=0; j<n; j++) {
			scanf("%d",&a[i][j]);
			if(a[i][j]) {
				if(!(h[i][a[i][j]]&l[j][a[i][j]]&g[G(i,j)][a[i][j]])) {
					return puts("-1"),0;//如果数独不合规直接无解。
				}
				h[i][a[i][j]]=l[j][a[i][j]]=g[G(i,j)][a[i][j]]=0;
				--c[s[i][j]];
				ans+=a[i][j]*s[i][j];
				++H[i].first;//标记当前行、列、宫格。
			}
		}
	}
	std::sort(H,H+n,std::greater<PII>());//greater 即重载,这里排序的意思是将 first 为第一关键字,second 为第二关键字,降序排列。
	dfs(0,0,0);
	printf("%d\n",F?ans:-1);
	return 0;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值