P1074 靶形数独

知识点: 搜索, 剪枝

原题面

题目要求:

给定一 \(9 \times 9\) 的方格图, 部分方格中的数已知
要求将此方格图填数, 满足下列要求:

  1. 每一个下图中所示 \(3 \times 3\)方格图中 不得出现重复的数
  2. 每一行不出现 重复的数
  3. 每一列不出现 重复的数
    同时, 每一个位置 都有 一个分值, 各位置分值按照下图所示 :

@text | center | 300x300

总分数即 每个方格上的分值和数字的乘积 的总和
求最大的总分数


分析题意:

似乎并没有什么 时间复杂度较低的方法,
能在保证正确性的情况下, 找到最优解 .

考虑使用搜索, 枚举各位置,
构造合法填数方案, 求每一合法填数方案分数的最大值

  • 关于不重复出现的判断 :

    题目要求 每一小九宫格, 每一行, 每一列都不出现重复数
    考虑对 每一小九宫格, 每一行, 每一列, 可以选择的数的集合进行状压.
    某一二进制上为 \(1\) , 代表此位置对应的数可以选择, 否则不可选择

    通过集合的性质 ,
    对于每一个位置, 其上述三集合的并集, 即为此位置可选择的数的集合
    选择 三个集合的并集 中的数 一定不会导致出现重复选择的情况
    按照此方法构造的矩阵, 一定合法

    可以使用 \(lowbit\) 运算 来枚举二进制位上的 \(1\) , 以减少时间复杂度

实现了 上述过程后, 即可取得 \(80\) 分的好成绩(大雾
似乎找不到别的 常规方法进行优化
结合题目给定模型的性质 进行考虑 :

  • 继续优化 :
     
    当一个位置是 \(0\) 时, 才需要填数
    显然 , 一个 \(0\) 位置 能够填入的数的个数, 会影响之后搜索的次数
    • 若一位置 可填入数的个数为 \(9\) , 从此位置可向下 \(dfs\) \(9\)
      若一位置 可填入数的个数为 \(2\) , 只需从此位置向下\(dfs\) \(2\) 次即可

故应优先选择 可填入的数 较少的位置 进行搜索
为便于实现 , 将每行按照 \(0\) 的个数 进行排序, 先从 \(0\) 较少的行开始搜

#include <cstdio> 
#include <ctype.h>
#include <algorithm>
#define lowbit(x) (x & - x)
#define max std::max 
const int MARX = 15;
const int W[MARX][MARX] =//每一个未知的价值 
{
	{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}
};
const int BEL[MARX][MARX] =//每一个位置 所在小矩阵的编号 
{
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 	{0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
 	{0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
 	{0, 1, 1, 1, 2, 2, 2, 3, 3, 3},
 	{0, 4, 4, 4, 5, 5, 5, 6, 6, 6},
 	{0, 4, 4, 4, 5, 5, 5, 6, 6, 6},
 	{0, 4, 4, 4, 5, 5, 5, 6, 6, 6},
 	{0, 7, 7, 7, 8, 8, 8, 9, 9, 9},
 	{0, 7, 7, 7, 8, 8, 8, 9, 9, 9},
 	{0, 7, 7, 7, 8, 8, 8, 9, 9, 9}
};
//=============================================================
struct Line
{
	int x, cnt;
}line[MARX];//记录, 每一行中 0的数量, 来确定搜索的顺序 
int ans, MAP[MARX][MARX], que[MARX * MARX][3];//que: 各位置搜索的顺序 
//sma:小矩形内 可选择数的状态; bigx,y:大矩形内 各行各列可选择数的状态
int usesma[MARX], usebigx[MARX], usebigy[MARX]; 
int Log2[1050];
//=============================================================
inline int read()
{
    int s = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0');
    return s * w;
}
bool cmp(Line fir, Line sec) {return fir.cnt < sec.cnt;}
void Prepare()
{
	for(int i = 1; i <= 9; i ++) Log2[(1 << i)] = i;//预处理log2 
	for(int i = 1; i <= 9; i ++) line[i].x = i;//各行的序号 
	for(int i = 1; i <= 9; i ++) //初始化 小矩形内 可选择数的状态, 大矩形内 各行各列可选择数的状态
	  usesma[i] = usebigx[i] = usebigy[i] = (1 << 10) - 2; 
	
	for(int x = 1; x <= 9; x ++)//读入矩阵, 并计算每行0的个数 
	  for(int y = 1; y <= 9; y ++)
	    MAP[x][y] = read(), 
		line[x].cnt += (MAP[x][y] == 0);
	
	for(int i = 1; i <= 9; i ++) //枚举各位置, 更新可选择数的状态
	  for(int j = 1; j <= 9; j ++)
	    usebigx[i] ^= (1 << MAP[i][j]) ,
		usebigy[j] ^= (1 << MAP[i][j]) ,
		usesma[BEL[i][j]] ^= (1 << MAP[i][j]);
	
	std :: sort(line + 1, line + 9 + 1, cmp);//按照0的个数, 对每一行进行排序 
	for(int i = 1, tot = 0; i <= 9; i ++)//根据排序后的结果, 确定搜索的顺序 
	  for(int j = 1; j <= 9; j ++)
		que[++ tot][1] = line[i].x,
		que[tot][2] = j;
}
void Dfs(int now, int sum)
{
	if(now > 81) {ans = max(ans, sum); return;}//所有位置 全部填完 
	
	int nowx = que[now][1], nowy = que[now][2], bel = BEL[nowx][nowy];//记录当前位置 
	if(MAP[nowx][nowy]) {Dfs(now + 1, sum + MAP[nowx][nowy] * W[nowx][nowy]); return ;}//当前位置 已被填过 
	
	int choose = (usesma[bel] & usebigx[nowx] & usebigy[nowy]);//三个 可选择的集合的交集, 即为此位置可选择的数的集合 
	for(int i = lowbit(choose); choose; choose -= i, i = lowbit(choose))//使用lowbit 取出可选择的数 
	{ 
	  if(i == 1) continue;//log2(1) = 0, 各位置不可填0, 故continue 
	  MAP[nowx][nowy] = Log2[i];//填入 
	  usesma[bel] ^= i, usebigx[nowx] ^= i, usebigy[nowy] ^= i;//更新可选择的数 的状态 
	  
	  Dfs(now + 1, sum + Log2[i] * W[nowx][nowy]);//搜索下一位置 
	  
	  MAP[nowx][nowy] = 0;//回溯 
	  usesma[bel] |= i, usebigx[nowx] |= i, usebigy[nowy] |= i;
	}
}
//=============================================================
int main()
{
	Prepare();
	Dfs(1, 0);
	printf("%d", ans ? ans : - 1);
	return 0;
}
posted @ 2019-11-12 16:48  Luckyblock  阅读(67)  评论(0编辑  收藏  举报