洛谷题单指南-进阶搜索-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、分值计算

可以在DFS过程中,计算分值,这样当找到一组答案可以快速更新最优解。

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

 

posted @   五月江城  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
历史上的今天:
2024-02-20 洛谷题单指南-递推与递归-P1259 黑白棋子的移动
2024-02-20 洛谷题单指南-递推与递归-P3612 [USACO17JAN] Secret Cow Code S
点击右上角即可分享
微信分享提示