【每日一题】【回溯+二进制优化】[USACO1.5] 八皇后 Checker Challenge C\C++\Java\Python3

news2024/9/17 8:34:25

P1219 [USACO1.5] 八皇后 Checker Challenge

[USACO1.5] 八皇后 Checker Challenge

题目描述

一个如下的 6 × 6 6 \times 6 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2   4   6   1   3   5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5 来描述,第 i i i 个数字表示在第 i i i 行的相应位置有一个棋子,如下:

行号 1   2   3   4   5   6 1\ 2\ 3\ 4\ 5\ 6 1 2 3 4 5 6

列号 2   4   6   1   3   5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 3 3 个解。最后一行是解的总个数。

输入格式

一行一个正整数 n n n,表示棋盘是 n × n n \times n n×n 大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

样例 #1

样例输入 #1

6

样例输出 #1

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

提示

【数据范围】
对于 100 % 100\% 100% 的数据, 6 ≤ n ≤ 13 6 \le n \le 13 6n13

题目翻译来自NOCOW。

USACO Training Section 1.5

做题要点

  1. 字典顺序排列
  2. 6 ≤ n ≤ 13 6 \le n \le 13 6n13
  3. 输出前 3 3 3 个解
  4. 每行、每列有且只有一个棋子,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
  5. 以上面的序列方法输出(题目给出了具体的答案输出规则)

做题思路

因为每行、每列有且只有一个棋子,那么模拟放棋子的情况。
因为答案是行号按顺序的,所以按行(从第一行放到第 n n n行可以满足题目序列方法输出要求)来依次放棋子是合理的。那么顺序为先行后列。

看一个普遍的情况,假设到了第 m m m行,行数固定了,那么看该行上每一列(也是从第一列开始到最后一列)的情况。
假设考虑到了第 m m m k k k列,首先应该判断该位置能不能放棋子:

  1. 如果可以那么将其放下,然后考虑第 m + 1 m+1 m+1 1 1 1列(因为该行已经放了棋子了,所以考虑下一行)。
  2. 如果不可以那么往后走,如果 k + 1 k+1 k+1不等于 n n n的话,考虑第 m m m k + 1 k+1 k+1列;否则,考虑第 m + 1 m+1 m+1 1 1 1
    重复以上步骤直到走完整个棋盘,然后判断棋盘上是否有 n n n枚棋子,如果有记为其中一种情况,否则不计入。

到这里已经有第一个答案了
由于题目要求字典顺序排列,就要考虑第二个答案和第一个答案之间的关系。

最粗暴(简单但吃速度)的做法为从新来一遍,直到走完整个棋盘,然后判断棋盘上是否有 n n n枚棋子且不为第一个答案(已有答案),那么就记为第二个答案,否则不计入。
然后以此类推得出三个答案…直到所有答案或再用其他办法跑出解的总个数。

字典顺序排列其实就是首先看第一个字符比较大小,如果相等比较下一个字符,否则大的为大。
那么按照字典序的思想,应该先改变最后一个字符(变大),也就是最后一行棋子的列数变大。
如果无法改变把最后一个字符变大,那么就把最后一个字符变最小,改变倒数第二个字符。
依次类推。即字典序逐步增大。

那么第一次走完整个棋盘后,拿掉最后一个棋子(也就是最后一行的棋子,假设原本在第 m m m k k k列),然后考虑第第 m m m k + 1 k+1 k+1列能不能放棋子,回到上述步骤。
这里就出现了一个新问题和老问题,如果棋盘上没有 n n n枚棋子或者该行无法放棋子了,怎么办?
也就说出现了至少有一行无法放下棋子。这说明放该行以前就把该行的所有情况ban掉了(无法放了)。
换句话说以前放的棋子策略是错的。
那么就需要从新放以前的棋子,再加上字典序的思想。首先调整的就是不能放棋子的那行的上一行。
按照这样的调整思路,如果不能放那就往回走,如果放满了自然就是一种情况,并且答案是按照字典序排序的。

将这种思想称为回溯.

接着说说如何判断该位置能不能放棋子
最直接的操作,行、列、副对角线、主对角线 各一个标记数组。
记为 r o w , c o l u m n , S u b _ d i a g o n a l , M a i n _ d i a g o n a l row , column,Sub\_ diagonal, Main\_ diagonal row,column,Sub_diagonal,Main_diagonal;
其中 r o w i = t r u e row_i = true rowi=true表示第 i i i行可以放,否则不行。
同理, c o l u m n i = t r u e column_i = true columni=true表示第 i i i列可以放,否则不行。
那么主副对角线需要找到对应关系。
这里直接给出。
同一副对角线上的格子,下标相加相等,例如第一行第二列和第二行第一列,加起来都为3
同一主对角线上的格子,下标相减相等
例如第一行第四列和第三行第六列, 1 − 4 = 3 − 6 = − 3 1-4 = 3-6 = -3 14=36=3
因为会出现负数,在程序中可以加上 n n n即可全为正数,对数组访问更方便。

二进制优化

对于判断该位置能不能放棋子的操作可以进行二进制优化。
可以优化空间复杂度
可以看到 6 ≤ n ≤ 13 6 \le n \le 13 6n13,也就是说 n n n不大,那么开出来的四个标记数组也不大。
如果换成一个数字,其中二进制的每一位都为一个标记,那么空间复杂度从一个数组变成一个数字了。

例如 二进制数字 0001000 0001000 0001000 对应的数组应该是 v [ 4 ] = t r u e v[4] = true v[4]=true其他全为 f a l s e false false类似这种对应关系。
如果要进行 v [ 3 ] = t r u e v[3] = true v[3]=true则对二进制数字对应位 或(|) 上1即可,最终变为 0001100 0001100 0001100

总结思路:

假设考虑到了第 m m m k k k列,首先应该判断该位置能不能放棋子:

  1. 如果可以那么将其放下,如果 m + 1 = = n m+1==n m+1==n记为一种情况(棋盘放满了),将其拿去,继续考虑第 m m m k + 1 k+1 k+1列的情况( k + 1 = = n k+1==n k+1==n的话,拿去 m − 1 m-1 m1行的棋子(记为 a a a列),继续考虑第 m − 1 m-1 m1 a + 1 a+1 a+1列);否则,考虑第 m + 1 m+1 m+1 1 1 1列(因为该行已经放了棋子了,所以考虑下一行)。
  2. 如果不可以那么往后走,如果 k + 1 k+1 k+1不等于 n n n的话,考虑第 m m m k + 1 k+1 k+1列;否则,进入第三个情况。
  3. 回到 m − 1 m-1 m1行有棋子的那一列(假设为 a a a列),将该棋子拿去,如果 a + 1 a+1 a+1不等于 n n n的话,继续考虑第 m − 1 m-1 m1 a + 1 a+1 a+1列,否则,考虑第 m − 1 m-1 m1 a a a列的第三种情况.
    重复以上过程直到回跳到了第 0 0 0行(棋盘外面).

时间复杂度分析

首先分析最简单的第一个棋子如果放在第一行 n n n种情况,到最后一个棋子肯定只有一种情况了。因为涉及到主对角线和副对角线的剪枝问题,该时间复杂度无法很好推出。
退一步,如果不考虑主对角线和副对角线的情况,那么该时间复杂度应该是 O ( n ! ) O(n!) O(n!)
在先如果考虑的话,第 k k k行可能减少 k 1 k_1 k1个情况
那么时间复杂度 O ( n ! − ∑ i = 1 n k i ) O(n! - \displaystyle\sum_{i=1}^{n} k_i) O(n!i=1nki)
但粗略分析,时间复杂度随 n n n的增大,后续会迅速增大(可能为指数增长或者低于 n n n倍高于 n − 3 n-3 n3倍增长)
具体跑程序分析可得下图
在这里插入图片描述

伪代码

在这里插入图片描述

核心代码对应思路

void Solution::dfs(int x){
    if(x == n+1){//棋盘被放满
        cnt++;//答案情况+1
        if(cnt <= 3) {//如果是前三个答案
            for (auto i: ans)//输出答案
                std::cout << i << ' ';
            std::cout << "\n";
            //return; //(可选)
        }
    }
    for(int y=1;y<=n;y++){//枚举每一列
        if(check(x,y)){//判断该位置能不能放棋子
            put_down(x,y);//放棋子,做标记
            dfs(x+1);//到下一行
            pick_up(x,y);//拿掉棋子,去掉标记
        }
    }
    //y == n+1 一行都放不下了进入第三个情况,回退到上一个dfs(x-1)
}

假设考虑到了第 m m m k k k列,首先应该判断该位置能不能放棋子:

  1. 如果可以那么将其放下,如果 m + 1 = = n m+1==n m+1==n记为一种情况(棋盘放满了),将其拿去,继续考虑第 m m m k + 1 k+1 k+1列的情况( k + 1 = = n k+1==n k+1==n的话,拿去 m − 1 m-1 m1行的棋子(记为 a a a列),继续考虑第 m − 1 m-1 m1 a + 1 a+1 a+1列);否则,考虑第 m + 1 m+1 m+1 1 1 1列(因为该行已经放了棋子了,所以考虑下一行)。
  2. 如果不可以那么往后走,如果 k + 1 k+1 k+1不等于 n n n的话,考虑第 m m m k + 1 k+1 k+1列;否则,进入第三个情况。
  3. 回到 m − 1 m-1 m1行有棋子的那一列(假设为 a a a列),将该棋子拿去,如果 a + 1 a+1 a+1不等于 n n n的话,继续考虑第 m − 1 m-1 m1 a + 1 a+1 a+1列,否则,考虑第 m − 1 m-1 m1 a a a列的第三种情况.
    重复以上过程直到回跳到了第 0 0 0行(棋盘外面).

return;是可选的原因为,放满 n n n个棋子后必定无法放 n + 1 n+1 n+1个棋子了。
所以不写return递归也肯定无法继续深入。
注:回溯写return出口是比较好的习惯

完整代码

C

#include <stdio.h>
#define re(i) (1<<(i))
const int N = 1e5;
int cnt , n , ans[20];
long long row,column,Sub_diagonal,Main_diagonal;
void dfs(int x){
    if(x == n+1){
        cnt++;
        if(cnt <= 3)
            for(int i=1;i<=n;i++)printf("%d%c",ans[i]," \n"[n==i]);
        return ;
    }
    for(int i=1;i<=n;i++){
        //二进制优化
        if((row&re(x)) || (column&re(i)) || (Sub_diagonal&re(i+x)) || (Main_diagonal&re(i-x+n)));
        else{
            row|=re(x);column|=re(i);Sub_diagonal|=re(i+x);Main_diagonal|=re(i-x+n);
            ans[x]=i;
            dfs(x+1);
            row^=re(x);column^=re(i);Sub_diagonal^=re(i+x);Main_diagonal^=re(i-x+n);
        }
    }
}
void init(){
    NULL;
}
int main() {
    scanf("%d",&n);
    init();
    dfs(1);
    printf("%d",cnt);
    return 0;
}

C++

#include <iostream>
#include <vector>
#include <cstring>
class Solution{
    int n,cnt;
    bool *row , *column;
    bool *Sub_diagonal,*Main_diagonal;
    std::vector<int>ans;
    void dfs(int x);
    void init();
    inline bool check(int ,int );
    inline void put_down(int,int);
    inline void pick_up(int,int);
public:
    void solve();
};
int main() {
    auto *solution = new Solution();
    solution->solve();
    return 0;
}
void Solution::dfs(int x){
    if(x == n+1){
        cnt++;
        if(cnt <= 3) {
            for (auto i: ans)
                std::cout << i << ' ';
            std::cout << "\n";
        }
    }
    for(int y=1;y<=n;y++){
        if(check(x,y)){
            put_down(x,y);
            dfs(x+1);
            pick_up(x,y);
        }
    }
}
inline bool Solution::check(int x,int y){
    return row[x] && column[y] && Sub_diagonal[x+y] && Main_diagonal[x-y+n];
}
void Solution::init() {
    std::cin >> n;
    row = new bool[n+1];memset(row,true,n+1);
    column = new bool[n+1];memset(column,true,n+1);
    Sub_diagonal = new bool[(n<<1)+1];memset(Sub_diagonal,true,(n<<1) + 1);
    Main_diagonal = new bool[n<<1];memset(Main_diagonal,true,n<<1);
    cnt = 0;
    ans.clear();
}
void Solution::solve() {
    init();
    dfs(1);
    std::cout << cnt ;
}
inline void Solution::put_down(int x, int y) {
    ans.push_back(y);
    row[x] ^= true;
    column[y] ^= true;
    Sub_diagonal[x+y] ^= true;
    Main_diagonal[x-y+n] ^= true;
}
inline void Solution::pick_up(int x, int y) {
    ans.pop_back();
    row[x] |= true;
    column[y] |= true;
    Sub_diagonal[x+y] |= true;
    Main_diagonal[x-y+n] |= true;
}

Java

import java.util.Scanner;
import java.util.Vector;

public class Main {
    static int n,cnt;
    static boolean[] row =new boolean[100];
    static boolean[] column =new boolean[100];
    static boolean[] Sub_diagonal =new boolean[100];
    static boolean[] Main_diagonal =new boolean[100];
    static Vector<Integer>v = new Vector<>();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        init();
        dfs(1);
        System.out.print(cnt);
    }

    public static void dfs(int x){
        if(x == n+1){
            cnt++;
            if(cnt <= 3){
                for(Integer i:v){
                    System.out.print(i + " ");
                }
                System.out.println();
            }
            return ;
        }
        for(int i=1;i<=n;i++){
            if(check(x,i)){
                put_down(x,i);
                dfs(x+1);
                pick_up(x,i);
            }
        }
    }
    public static void init(){
        cnt = 0;
        for(int i=1;i<100;i++){
            row[i] = column[i] = Sub_diagonal[i] = Main_diagonal[i] = true;
        }
    }
    public static boolean check(int x,int y){
        return row[x] && column[y] && Sub_diagonal[x+y] && Main_diagonal[x-y+n];
    }
    public static void put_down(int x,int y){
        v.add(y);
        row[x] ^= true;
        column[y] ^= true;
        Sub_diagonal[x+y] ^= true;
        Main_diagonal[x-y+n] ^= true;
    }
    public static void pick_up(int x,int y){
        v.removeLast();
        row[x] ^= true;
        column[y] ^= true;
        Sub_diagonal[x + y] ^= true;
        Main_diagonal[x - y + n] ^= true;
    }
}

Python3(不推荐)
因为最后一个点需要打表才能过

n = int(input())
row = [0 for i in range(200)]
column = [0 for i in range(200)]
Sub_diagonal  = [0 for i in range(200)]
Main_diagonal = [0 for i in range(200)]
cnt = 0

def printf():
    global cnt
    for i in range(1,n+1):
        print(row[i], end=' ')
    print()
def dfs(x):
   global cnt
   if x == n+1:
       cnt=cnt+1
       if cnt <= 3:
            printf()
       return
   for y in range(1,n+1):
       if column[y] == 0 and Sub_diagonal[x+y] == 0 and Main_diagonal[x-y+n] == 0:
           row[x] = y
           column[y] = 1
           Sub_diagonal[x+y] = 1
           Main_diagonal[x-y+n] = 1
           dfs(x+1)
           column[y] = 0
           Sub_diagonal[x+y] = 0
           Main_diagonal[x-y+n] = 0

if n==13:
    print('1 3 5 2 9 12 10 13 4 6 8 11 7')
    print('1 3 5 7 9 11 13 2 4 6 8 10 12')
    print('1 3 5 7 12 10 13 6 4 2 8 11 9')
    print(73712)
    exit(0)
dfs(1)
print(cnt)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1961572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python设置Excel单元格中的部分文本颜色

文章目录 一、概述二、效果三、示例 一、概述 openpyxl &#xff08;目前&#xff09;不支持设置单元格内部分字体颜色 xlsxwriter 支持设置单元格内部分字体颜色&#xff08;创建新的Excel&#xff09; 二、效果 三、示例 """ Python设置Excel单元格中的部分…

昇思 25 天学习打卡营第 24 天 | MindSpore Pix2Pix 实现图像转换

1. 背景&#xff1a; 使用 MindSpore 学习神经网络&#xff0c;打卡第 24 天&#xff1b;主要内容也依据 mindspore 的学习记录。 2. PixPix 介绍&#xff1a; MindSpore 的 Pix2Pix 图像转换 介绍 Pix2Pix是基于条件生成对抗网络&#xff08;cGAN, Condition Generative Ad…

Oracle如何跨越incarnation进行数据恢复

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG、Mongodb数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享…

Skywalking 入门与实战

一 什么是 Skywalking? Skywalking 时一个开源的分布式追踪系统&#xff0c;用于检测、诊断和优化分布式系统的功能。它可以帮助开发者和运维人员深入了解分布式系统中各个组件之间的调用关系、性能瓶颈以及异常情况&#xff0c;从而提供系统级的性能优化和故障排查。 1.1 为…

笑谈“八股文”,人生不成文

一、“八股文”在实际工作中是助力、阻力还是空谈&#xff1f; 作为现在各类大中小企业面试程序员时的必问内容&#xff0c;“八股文”似乎是很重要的存在。但“八股文”是否能在实际工作中发挥它“敲门砖”应有的作用呢&#xff1f;有IT人士不禁发出疑问&#xff1a;程序员面试…

AcWing3302. 表达式求值

代码解释 while(j<str.size()&&isdigit(str[j])){xx*10str[j]-0;}把字符串中里面连续的数字转化为int类型变量&#xff0c;比如输入996/3328,正常的挨个字符扫描只能扫到’9’,‘9’,‘6’,但是按照上面代码的算法是重新开了一个循环&#xff0c;直接把’9’,‘9’,…

【网络请求调试神器,curl -vvv 返回都有什么】

curl -vvv 是一个用于在命令行中执行 HTTP 请求的命令&#xff0c;其中 -vvv 是一个选项&#xff0c;用于启用详细的调试输出。 vvv: 这是一个选项&#xff0c;表示启用详细的调试输出。每个 v 增加调试信息的详细程度&#xff0c;vvv 是最高级别的详细输出。 详细输出包括&a…

【shell脚本快速一键部署项目】

目录 一、环境拓扑图二、主机环境描述三、注意四、需求描述五、shell代码的编写六、总结 一、环境拓扑图 二、主机环境描述 主机名主机地址需要提供的服务content.exam.com172.25.250.101提供基于 httpd/nginx 的 YUM仓库服务ntp.exam.com172.25.250.102提供基于Chronyd 的 NT…

GPU池化:点燃Jupyter Notebook中的AI算力之火

数据科学的火花在Jupyter Notebook中点燃&#xff0c;而GPU的加入&#xff0c;让这火焰更加炽热&#xff01;随着人工智能领域的飞速发展&#xff0c;利用GPU加速已成为数据科学和机器学习领域的新常态。 今天&#xff0c;我们要探索的&#xff0c;是Jupyter Notebook与GPU池化…

PHP学习:PHP基础

以.php作为后缀结尾的文件&#xff0c;由服务器解析和运行的语言。 一、语法 PHP 脚本可以放在文档中的任何位置。 PHP 脚本以 <?php 开始&#xff0c;以 ?> 结束。 <!DOCTYPE html> <html> <body><h1>My first PHP page</h1><?php …

spaCy语言模型下载

spaCy 是一个基于 Python 编写的开源自然语言处理&#xff08;NLP&#xff09;库&#xff0c;它提供了一系列的工具和功能&#xff0c;用于文本预处理、文本解析、命名实体识别、词性标注、句法分析和文本分类等任务。 spaCy支持多种语言模型对文本进行处理&#xff0c;包括中文…

自己在Vmware中搭建mqtt服务器

前言 在学习某个HMI的使用的时候&#xff0c;这个HMI带有MQTT功能&#xff0c;就想着自己是不是能够搭建一个自己的MQTT的服务器呢&#xff1f; 一、mqtt 自己搭建之一&#xff1a;Mosquitto 自己搭建MQTT服务器需要安装和运行MQTT服务软件&#xff0c;比如常用的是Mosquitto…

Tkinter简介与实战(1)

Tkinter简介与实战---实现一个计算器 Tkinter简介安装环境和安装命令WindowsmacOSLinux 注意事项使用正确的包管理器&#xff1a;检查安装完整性&#xff1a;更新 Python&#xff1a;使用虚拟环境&#xff1a; 一个实战例子-----计算器1.创建窗口&#xff1a;2.创建 GUI 组件&a…

学习大数据DAY27 Linux最终阶段测试

满分&#xff1a;100 得分&#xff1a;72 目录 一选择题&#xff08;每题 3 分&#xff0c;共计 30 分&#xff09; 二、编程题&#xff08;共 70…

ANSYS仿真DDR4的眼图

1 眼图的基本知识 对于数字信号&#xff0c;高低电平转换可以组合在多个序列中。以3位为例&#xff0c;总共有000-111和8种组合。在时域中&#xff0c;根据某个参考点对足够多的序列进行对齐&#xff0c;然后将波形叠加形成眼图&#xff0c;如下图所示。 图&#xff1a;眼图中…

JavaScript object find 示例

https://andi.cn/page/621631.html

从信息论的角度看微博推荐算法

引言 在数字时代&#xff0c;推荐系统已成为社交媒体和其他在线服务平台的核心组成部分。它们通过分析用户行为和偏好&#xff0c;为用户提供个性化的内容&#xff0c;从而提高用户满意度和平台的参与度。推荐系统不仅能够增强用户体验&#xff0c;还能显著提升广告投放的效率…

angular入门基础教程(一)环境配置与新建项目

ng已经更新到v18了&#xff0c;我对他的印象还停留在v1,v2的版本&#xff0c;最近研究了下&#xff0c;与react和vue是越来越像了&#xff0c;所以准备正式上手了。 新官网地址:https://angular.cn/ 准备条件 nodejs > 18.0vscodeng版本18.x(最新的版本) {"name&qu…

C# Unity 面向对象补全计划 之 继承(字段与属性)

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列旨在通过补全学习之后&#xff0c;给出任意类图都能实现并做到逻辑上严丝合缝 Q&#xff1a;为什么要单讲继承字段与属性&#xff0c;不讲继承方法了吗&#x…

【SuperMap GIS 信创部署系列】-- 金蝶V10中间件

⼀、安装包获取 本⽂以10.2.1版本安装为例&#xff0c;官⽹下载iserver war包即可。 下载地址&#xff1a;http://support.supermap.com.cn/DownloadCenter/DownloadPage.aspx?id1852 ⼆、部署 iServer.war 1.解压安装包 将下载的supermap-iserver-10.2.1-war.zip包进⾏解…