洛谷题单指南-进阶搜索-P1074 [NOIP 2009 提高组] 靶形数独
原题链接:https://www.luogu.com.cn/problem/P1074
题意解读:求所有数独方案中得分最高的,9*9的矩阵,每个位置有不同得分,数独的规则是所有行、列,3*3矩阵内每个数字都是1~9不重复。
解题思路:
1、搜索顺序
普通方法:从左到右,从上到下,对于每一个空格,枚举所有能填的数,依次填入再DFS。
优化方法:每个格子能填的数有多有少,显然如果优先处理那些可填数种类最少的,可以减少搜索状态扩充,快速判断出不可行的方案,提前结束DFS。
2、状态表示
对于每一个格子,要知道能填哪些数,就需要保存行、列、3*3矩阵的状态,如何快速的对状态进行处理,就是这道题的又一个关键点。
设row[i]表示第i行的数字情况,col[i]表示第i列的数字情况,block[i][j]表示第i行第j列3*3矩阵的数字情况
行、列、矩阵的数字情况是一个二进制整数,对应位为1表示这个数字已经出现过,对应位为0表示这个数字还没有出现过
如:第i行存在数字k,则有row[i] |=1<< (k -1)
要计算第(i, j)位置可以填的数,可以令t = ~(row[i] | col[j] | block[i / 3][j / 3]) & 0x1ff,这样以来可以填的数在t中对应位置是1,已经填的数在t中对应位置是0
通过统计t中1的个数就能知道(i,j)位置能填的数的个数,枚举t中所有1代表值,就能知道要填的是什么数
如:t = 12,二进制是1100(第3/4位为1),显然可以填的数是3和4,通过lowbit(t)得到4=2^2,即可以填的数是2+1=3,t - lowbit(t)之后为8,再通过lowbit(t)得到8=2^3,即可以填的数是3+1=4,注意第一个二进制位是2^0,所以对应指数加1才是能填的数。
为了快速计算一个数中二进制1的个数、以及一个数2^x的x值是多少,可以提前进行预处理。
当某个位置计算得到的t为0,说明没有可以填的数,应该提前结束DFS。
3、分值计算
100分代码:
#include <bits/stdc++.h>
using namespace std;
int g[9][9];
//row[i]表示第i行的数字情况,col[i]表示第i列的数字情况,block[i][j]表示第i行第j列3*3区域的数字情况
//行、列、区块的数字情况是一个二进制整数,对应位为1表示这个数字已经出现过,对应位为0表示这个数字还没有出现过
int row[9], col[9], block[3][3];
int blank; //一共有多少个空格
int sum, ans;
int score[9][9] =
{
{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 lg2[1 << 9]; //lg2[2^i] = i
int one[1 << 9]; //one[i]表示i的二进制表示中1的个数
int lowbit(int x)
{
return x & -x;
}
void dfs(int cnt, int sum)
{
if(cnt == blank) //所有空格都填完了
{
ans = max(ans, sum);
return;
}
//找到可填数字最少的位置
int x = -1, y = -1, minv = 10, stat = 0;
for(int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
if(g[i][j]) continue;
int t = ~(row[i] | col[j] | block[i / 3][j / 3]) & 0x1ff; //当前位置可以填的数字
int v = one[t]; //当前位置可以填的数字个数,即t中1的个数
if(v < minv) x = i, y = j, minv = v, stat = t;
}
}
if(!stat) return; //存在不能填数的位置
for(int k = stat; k != 0; k -= lowbit(k)) //枚举当前位置可以填的数字num,lowbit(k)即2^num
{
int num = lg2[lowbit(k)]; //当前位置填的数字,num
g[x][y] = num + 1; //填入数字,注意num是从0开始的,实际填入的数字是num+1
//更新行、列、区块的数字情况
row[x] ^= 1 << num, col[y] ^= 1 << num, block[x / 3][y / 3] ^= 1 << num;
dfs(cnt + 1, sum + g[x][y] * score[x][y]); //递归下一个位置
//恢复行、列、区块的数字情况
row[x] ^= 1 << num, col[y] ^= 1 << num, block[x / 3][y / 3] ^= 1 << num;
g[x][y] = 0; //恢复当前位置为空
}
}
int main()
{
for(int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
cin >> g[i][j];
if(!g[i][j]) blank++;
else
{
sum += g[i][j] * score[i][j]; //计算已经填入的数字的得分
row[i] |= 1 << (g[i][j] - 1);
col[j] |= 1 << (g[i][j] - 1);
block[i / 3][j / 3] |= 1 << (g[i][j] - 1);
}
}
}
//预处理lg2, 便于快速计算2^x对应的x
lg2[1] = 0;
for(int i = 1; i < 9; i++) lg2[1 << i] = i;
//预处理one, 便于快速计算一个二进制数中1的个数
for(int i = 1; i < 1 << 9; i++) one[i] = one[i - lowbit(i)] + 1;
dfs(0, sum);
if(ans) cout << ans << endl;
else cout << -1 << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
2024-02-20 洛谷题单指南-递推与递归-P1259 黑白棋子的移动
2024-02-20 洛谷题单指南-递推与递归-P3612 [USACO17JAN] Secret Cow Code S