首页 > 其他分享 >题解:P10481 Sudoku

题解:P10481 Sudoku

时间:2024-07-27 22:50:49浏览次数:12  
标签:Sudoku 运算 题解 P10481 位置 蓝书 搜索 sukodu 优化

Sudoku

来自蓝书

思路

优化搜索顺序,位运算常数优化。

优化搜索顺序

数独问题的搜索很简单,“搜索状态”就是每个位置上填了什么数。在每个状态下,选择一个未填位置,检查那些填入仍能满足数独条件(合法)的数字。构成的新的局面即为该状态的“分支”。

足够暴力的写法为依次对每个位置进行搜索,但盲目搜索的效率肯定相当低,如果让人类玩数独的话,我们会先填已经能唯一确定的位置,这样就可以减少其他位置的合法数字,使数独的复杂程度降低。

在搜索中,我们也可以采取类似的策略,从能填的合法数字最少的位置向下搜索,这样就能大大减少分支数量,也就是优化顺序的剪枝方法。

常数优化

  1. 对于每行每列每个九宫格,分别用一个 9 位二进制保存有那些数能用。
  2. 对于每个数字,把它所在的行、列、九宫格的三个二进制数做按位与运算,就可以知道该位置可以填那些数,再用 lowbit 运算就可以将能填的数字取出。
  3. 当一个位置上填上某个数后,把该位置所在行、列、九宫格的二进制数对应位改为 0 更新装态,回溯时改为 1 还原现场。可以联想利用异或的性质。
  4. 用 lowbit 运算预处理每个 9 位二进制数有几位 1 ,即可快速确定能填的合法数字最少的位置
  5. 打表确定用 lowbit 运算取出来的位置对应的数字

代码

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 1 << 9;

int cnt[N], f[N], T, sukodu[9][9], x[9], y[9], z[9];
char c;

int gon(int i, int j) { return i / 3 * 3 + j / 3; }

int get_cnt(int i)
{
    int ans = 0;
    while(i)
        ans++, i -= (i & -i);
    return ans;
}

void work(int i, int j, int v)
{
    x[i] ^= (1 << v);
    y[j] ^= (1 << v);
    z[gon(i, j)] ^= (1 << v);
}

bool dfs(int tot)
{
    if (!tot) return true;

    int t = 10, nx, ny;

    for (int i = 0; i < 9; i++)
        for (int j = 0; j < 9; j++)
        {
            if (!sukodu[i][j])
            {
                int w = x[i] & y[j] & z[gon(i, j)]; // 优化2
                if (cnt[w] < t)
                    nx = i, ny = j, t = cnt[w];
            }
        }

    int w = x[nx] & y[ny] & z[gon(nx, ny)];
    while(w)
    {
        int now = f[w & -w];
        sukodu[nx][ny] = now + 1;
        work(nx, ny, now);
        if (dfs(tot - 1)) return true;

        sukodu[nx][ny] = 0;
        work(nx, ny, now);
        w -= w & -w;
    } 
    return false;
}

int main()
{
    for (int i = 0; i < N; i++) cnt[i] = get_cnt(i); //优化4
    for (int i = 0; i < 9; i++) f[1 << i] = i;  // 优化5

    scanf("%d", &T);
    while(T--)
    {
        int tot = 0;
        for (int i = 0; i < 9; i++) x[i] = y[i] = z[i] = N - 1; // 优化1

        for (int i = 0; i < 9; i++)
            for (int j = 0; j < 9; j++)
            {
                cin >> c;
                sukodu[i][j] = c - '0';
                if (sukodu[i][j]) work(i, j, sukodu[i][j] - 1); // 优化3
                else tot++;
            }

        if (dfs(tot))
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                    printf("%d", sukodu[i][j]);
                puts("");
            }
    }
    return 0;
}

后记

这道题目数据很水,完全不需要优化就能过,这很明显
但作为蓝书的题目,我想应该要有蓝书的写法,我是蒟蒻,我写不来蓝书的方法,我想也有人不会,所以发出来给像我这样的蒟蒻参考
位运算优化会快很多,我的代码实测为 4ms ,比大多数题解快一些

标签:Sudoku,运算,题解,P10481,位置,蓝书,搜索,sukodu,优化
From: https://www.cnblogs.com/faruzan/p/18327650

相关文章

  • 2023CSP-j复赛题解
    csp-j题解update:2024.6.18-2024.6.25:重构题解第一题:小苹果原题洛谷P9748思路n表示当前长度求几天取完:每天取走\((n-1)/3+1\)个苹果,记录几天取完第\(n\)个苹果第几天被取走:当\(n\bmod3=0\)时被取走时间复杂度约为\(O(\log_n)\)#include<iostream>......
  • [ARC105C] Camels and Bridge 题解
    [ARC105C]CamelsandBridge题解出自梦熊比赛后,梦熊比赛出原题了,忘周知。也许更好的阅读体验思路全排列,差分约束,二分。全排列\(n\leq8\),且要指定顺序,易想到用全排列枚举所有状态。差分约束在全排列之后,需要求得每种状态的最短距离。定义所有骆驼的编号的集合为\(S\)......
  • ABC 364 F - Range Connect MST 题解
    一副扑克牌,去掉1到K,剩下就是我,赛后十秒过,我是joker。......
  • CF613E Puzzle Lover 题解
    Description给定一个\(2\timesn\)的矩阵,每个位置上有一个小写字母。有一个长度为\(k\)的小写字符串\(w\),询问矩阵中有多少条有向路径满足以下条件:路径上的字母连起来恰好为\(w\)。不多次经过同一个位置。只能向上下左右四个方向走。\(n,k\le2\times10^3\),答案......
  • IOI2022 邮局 加强版 题解
    [IOI2000]邮局加强版题解考虑动态规划,设\(f_{i,j}\)为经过了\(i\)个村庄,正在建第\(j\)​个邮局的最优距离。以及\(w_{i,j}\)表示区间\([i,j]\)​内建一个邮局时的距离总和。\(a\)数组表示每个村庄的坐标编号。朴素版状态转移方程:\[f_{i,j}=\min(f_{i,j},f_{k,j......
  • 07-25 题解
    07-25题解原题出处(按顺序):CF1556ECF1234FP9746CF1316EP3651CF17CCF1842HA转化:括号序列如果\(a_i\>b_i\),则有\(a_i-b_i\)个左括号如果\(a_i\<b_i\),则有\(b_i-a_i\)个右括号(第一个+1的位置一定在第一个-1的位置的左侧,所以情况一用左括号......
  • 第二次测试部分题解 (c,d,g)
    c-一个欧拉函数模板题 1#include<iostream>2usingnamespacestd;34intmain()5{6intn;7cin>>n;8intr=n;9for(inti=2;i*i<=n;i++)10{11if(n%i==0)12{13r......
  • 第二次测试部分题解
    A——暴力枚举计数就好了,可以参考这段代码#include<bits/stdc++.h>usingnamespacestd;#definelllonglong#definemod100003#defineMAN1000100charstr[10];intans;voidok(){ ans=0; intlen=0; for(inti=0;str[i]!='\0';i++) len++; if(l......
  • ABC260F 题解
    题面根据题目描述,原图为二分图,设两侧点集为\(S,T\),大小为\(s,t(s\le3\times10^5,t\le3\times10^3)\)。注意到有四元环当且仅当\(T\)中存在一个点对\((a,b)\)同时和\(S\)中的某两个点连边。可以先考虑暴力,一种想法是:考虑枚举\(S\)中的点\(c\),设和\(c\)连边的点......
  • ABC263F 题解
    题面注意到把对局在图上表示出来是一颗满二叉树(叶节点为选手,其他点为对局),可以考虑树形dp。设\(x\)为\([l_x,r_x]\)之间选手的比赛,且该节点到叶子结点距离\(d_x\)。设\(f(x,p)\)表示胜者为\(p\)的最大钱数,有转移:\[\begin{aligned}f(x,p)&=f(lson(x),p)+g(rson(x))+a......