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;
}