首页 > 其他分享 >摆放棋子

摆放棋子

时间:2022-11-13 20:46:02浏览次数:64  
标签:横边 格子 int 路径 摆放 枚举 棋子 竖边

摆放棋子

给定一个 $n \times m$ 的国际象棋棋盘(即一个 $n \times m$ 的方格矩阵)。

我们知道传统国际象棋中,主教(象)的行走规则是只能斜走,格数不限,但不可转向。

现在,我们对主教进行了修改,不妨称加强后的主教为大主教。

大主教仍然只能斜走,格数不限,但是当其走到棋盘边缘的方格时,它可以发生反射并继续移动,注意,如果没有走到棋盘边缘,则不能自行改变移动方向

更具体地说,当它到达棋盘边(但并非角)上的方格时,它可以将移动方向改变 $90$ 度并继续移动,当它到达棋盘角上的方格时,它可以将移动方向改变 $180$ 度并继续移动。

请你计算,棋盘中共有多少种不同的移动轨迹。

输入格式

共一行,包含两个整数 $n,m$。

输出格式

一个整数,表示棋盘中移动轨迹的数量。

数据范围

前 $6$ 个测试点满足 $2 \leq n,m \leq 4$。
所有测试点满足 $2 \leq n,m \leq {10}^{6}$。

输入样例1:

3 4

输出样例1:

2

输入样例2:

3 3

输出样例2:

3

 

解题思路

  由于证明比较麻烦,这里直接说结论,详细证明见参考资料中的链接。

  每一条路径都是一个循环路径,并且一定会碰到边界的某个格子,同时每一个边界的格子最多只会出现在某个路径当中。意味着边界的每一个格子只会唯一对应某条路径,不会出现多条路径包含同一个边界格子这种情况。一条路径可能会对应边上多个格子,反过来边界上某些格子可能会对应同一条路径。因此如果想要统计路径的数量,就可以统计四个边界的格子中一共对应几条不同的路径,这个就可以用并查集来实现。

  枚举矩形上下两条横边的格子(这里统一规定矩形的长大于等于宽),把属于同一个路径上的格子进行合并。

  比如上图中的红色格子,从红色格子往两个方向走分别可以走到蓝色格子的位置,这时就将红色格子与两个蓝色格子进行合并,某条路径会经过这个三个格子。

  矩形的左右两条竖边就不用枚举了,因为可以发现从竖边的格子往两个方向走一定会走到两条横边的某个格子并进行合并,而在枚举到这两个横边的格子时就已经将这个竖边的格子合并了,因此不用再枚举竖边的格子了。

  在枚举横边的格子时需要分成两种情况。当枚举从格子向左的方向走时,要以矩形的宽$n$为分界,当格子的纵坐标$i \leq n$时会走到左边的竖边,当$i < n$时会走到下边的横边。

  当枚举从格子向右的方向走时,要以从右边数第$n$个格子(即从左边数第$m-n+1$个格子)为分界,当格子的纵坐标$i \geq m - n + 1$时会走到右边的竖边,当$i > n$时会走到下边的横边。

  最后由于格子是一个二维坐标,因此还需要对四条边上的格子进行标号,这里从左上角开始进行顺时针标号。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 4e6 + 10;
 5 
 6 int n, m;
 7 int fa[N];
 8 
 9 int find(int x) {
10     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
11 }
12 
13 int get(int x, int y) {
14     if (x == 1) return y;   // 格子在上面的横边
15     else if (y == m) return m + x - 1;  // 格子在右边的竖边
16     else if (x == n) return m + n + m - y - 1;  // 格子在下面的横边
17     else return m + m + n - 1 + n - x - 1;  // 格子在左边的竖边
18 }
19 
20 int main() {
21     cin >> n >> m;
22     for (int i = 1; i <= 2 * (n + m) - 4; i++) {
23         fa[i] = i;
24     }
25     
26     if (n > m) swap(n, m);
27     for (int i = 1; i <= m; i++) {
28         // 向左的方向
29         if (i <= n) {   // 分界线左边
30             fa[find(get(1, i))] = find(get(i, 1));
31             fa[find(get(n, i))] = find(get(n - i + 1, 1));
32         }
33         else {  // 分界线右边
34             fa[find(get(1, i))] = find(get(n, i - n + 1));
35             fa[find(get(n, i))] = find(get(1, i - n + 1));
36         }
37         // 向右的方向
38         if (m - i + 1 <= n) {   // 分界线右边
39             fa[find(get(1, i))] = find(get(m - i + 1, m));
40             fa[find(get(n, i))] = find(get(n - m + i, m));
41         }
42         else {  // 分界线左边
43             fa[find(get(1, i))] = find(get(n, i + n - 1));
44             fa[find(get(n, i))] = find(get(1, i + n - 1));
45         }
46     }
47     
48     int ret = 0;
49     for (int i = 1; i <= 2 * (n + m) - 4; i++) {
50         if (fa[i] == i) ret++;
51     }
52     cout << ret;
53     
54     return 0;
55 }

  这里还有一种数学解法,答案就是 gcd(n - 1, m - 1) + 1 ,具体证明就更加复杂了,这个当作结论来记好了。可以参考下面链接:

Solutions for Codeforces Beta Round #68: https://codeforces.com/blog/entry/1731

 

参考资料

  AcWing 4718. 摆放棋子(AcWing杯 - 周赛):https://www.acwing.com/video/4530/

标签:横边,格子,int,路径,摆放,枚举,棋子,竖边
From: https://www.cnblogs.com/onlyblues/p/16886845.html

相关文章

  • Luogu P3182 [HAOI2016]放棋子
    题目链接:​​传送门​​题目说了每行有一个障碍两个障碍不在同一行也不在同一列那障碍放哪里就没关系了矩阵都不用输入或者这样理解:交换矩阵的某两行对答案是没有影响......
  • [编程题]堆棋子 中位数
    ​​https://www.nowcoder.com/questionTerminal/27f3672f17f94a289f3de86b69f8a25b​​由于x[]和y[]是独立的,所以贡献可以单独考虑如果选出k个,x[1...k]那么肯定是去到中位......
  • Go语言版黑白棋(十):棋子个数统计、胜负判断
    功能说明增加棋子个数统计、胜负判断功能,游戏结束后可选择继续游戏。示例代码packagemainimport("fmt""os""strconv""unsafe""github.com/mattn/go-g......
  • Go语言版黑白棋(七):初始化棋子、改变角色
    功能说明启动程序时,棋盘默认有黑白棋各2枚,落子时,黑白子交替下(角色切换)原理说明示例代码packagemainimport("fmt""os""unsafe""github.com/mattn/go-gtk......