剪格子

剪格子

如下图所示,$3 \times 3$ 的格子中填写了一些整数。

 

我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是 $60$。

本题的要求就是请你编程判定:对给定的 $m \times n$ 的格子中的整数,是否可以分割为两个连通的部分,使得这两个区域的数字和相等。

如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。

如果无法分割,则输出 $0$。

 

输入格式

第一行包含两个整数 $m,n$ ,表示表格的宽度和高度。

接下来是 $n$ 行,每行 $m$ 个正整数,用空格分开。

输出格式

在所有解中,包含左上角的分割区可能包含的最小的格子数目。

如果无法分割,则输出 $0$。

数据范围

$1 \leq n,m < 10$,
格子内的数均在 $1$ 到 $10000$ 之间。

输入样例1:

3 3
10 1 52
20 30 1
1 2 3

输出样例1:

3

输入样例2:

4 3
1 1 1 1
1 30 80 2
1 1 1 100

输出样例2:

10

解题思路

  这道题很容易想到深搜,但也有几个难点。

  首先要保证剪掉格子后,只有两个连通块。

  比如下图,如果剪掉的是红色区域的格子,那么最后会得到三个连通块。

   判断是否只有两个连通块的方法需要用到并查集。我们会把剪掉的格子放入一个集合中,然后用总格子的数量减去这个集合元素的数量,那么就会得到剩余未被剪掉格子的数量,同时一开始这些未被剪掉的格子各种都是一个独立的连通块,数量就是未被剪掉格子的数量。我们就是去看看能否把这些格子都并成一个连通块。

  然后遍历每一个格子,如果这个格子不属于这个集合的元素(意味着这个格子未被剪掉),那么就检查这个格子的四个方向的相邻格子,如果相邻的格子未被剪掉,并且这在并查集中的根节点不同(两个格子还没并在一起),那么就把其中一个根节点并到另一个根节点,同时连通块数量减$1$。最后,如果这些未被剪掉格子所得的连通块数量为$1$,说明这些未被剪掉格子是连通的,否则就不连通。

  另外一个问题是,我们剪的格子也是要满足连通的。我们一般的搜索顺序是从上一个搜到的格子往四个方向继续搜,但这样会有一个问题,就是搜到的格子都是一笔画的,也就是说这种搜索方式搜出来的是一条路径而不是一个连通块。

  上面的图就是这种搜索方式的结果,如果是下面这种图,这种搜索方式是搜不出来的:

  可以发现对于这种情况,无论是一笔还是两笔都是无法画完的,需要画多笔。

  因此我们应该从左上角开始搜,在搜索的时候维护一个集合,就是上面提到的集合,这个集合存放已剪且连通的格子。我们每次搜索的时候不是从上一个搜到的格子搜,而是从这个集合的每一个格子搜,这样才可以搜到满足多笔的情况。

  枚举集合中的每一个格子,然后对于每一个格子都往四个方向扩展,判断相邻的格子能否往下搜,如果可以的话,就将这个格子加入集合中,继续往下搜;回溯的时候把这格子从集合删除,继续遍历集合中的其他格子。

  这里还需要剪支。就是对于某个已经剪过的连通块,那么之前一定是已经搜过这种情况的了,我们就没必要再对这种情况搜一次了,因此我们开一个哈希表来记录每次搜索过的情况,对于同一个情况应该得到同一个结果,不同的情况得到不同的结果。因为每一个情况都是一个二维矩阵,因此我们需要状态压缩,方法是字符串哈希,把每种情况当作一个字符串来处理。因为集合中的点都是乱序的,为了保证一致性,我们先对集合中的格子进行排序,然后把每个格子的坐标组成一个字符串,进行哈希。

  最后AC代码如下:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <unordered_set>
  4 #include <algorithm>
  5 using namespace std;
  6 
  7 typedef unsigned long long ULL;
  8 typedef pair<int, int> PII;
  9 
 10 const int N = 20, P = 131;
 11 
 12 int n, m, ans = N * N;
 13 int graph[N][N];
 14 bool vis[N][N];
 15 int sum;
 16 int fa[N * N];
 17 PII a[N * N];
 18 unordered_set<ULL> st;
 19 int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
 20 
 21 int find(int x) {
 22     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
 23 }
 24 
 25 bool is_connect(int cnt) {  // 判断剩余部分是否是一个连通块
 26     for (int i = 0; i < n * m; i++) {
 27         fa[i] = i;
 28     }
 29     
 30     int num = n * m - cnt;  // 一开始每一个未被剪得格子都是单独的一个连通块
 31     
 32     // 遍历每一个格子
 33     for (int i = 0; i < n; i++) {
 34         for (int j = 0; j < m; j++) {
 35             if (!vis[i][j]) {   // 格子未被剪掉
 36                 for (int k = 0; k < 4; k++) {   // 往四个方向扩展
 37                     int x = i + dx[k], y = j + dy[k];
 38                     if (x >= 0 && x < n && y >= 0 && y < m && !vis[x][y]) {
 39                         int p1 = find(x * m + y), p2 = find(i * m + j); // 求这两个格子所在并查集的父节点
 40                         if (p1 != p2) {     // 这两个格子还不再同一个并查集中
 41                             fa[p1] = p2;    // 把这两个合并,意味着这两个格子在同一个连通块
 42                             num--;          // 连通块数量减1
 43                         }
 44                     }
 45                 }
 46             }
 47         }
 48     }
 49     
 50     return num == 1;    // 最后判断连通块数量是否只有一个
 51 }
 52 
 53 bool is_exist(int cnt) {    // 判断某种情况是否已经搜过
 54     PII tmp[N * N];
 55     memcpy(tmp, a, sizeof(a));  // 因为要排序,会改变原集合中元素的顺序,因此进行备份
 56     
 57     sort(tmp, tmp + cnt);
 58     
 59     ULL key = 0;    // 这种情况的哈希值
 60     for (int i = 0; i < cnt; i++) {
 61         key = key * P + tmp[i].first + 1;   // 哈希不能有0,因为一个0和两个0得到的结果是一样的
 62         key = key * P + tmp[i].second + 1;
 63     }
 64     
 65     if (st.count(key)) return true; // 如果这种情况出现过返回true
 66     st.insert(key); // 否则这种情况未出现过,把这种情况加入哈希表中,返回false
 67     return false;
 68 }
 69 
 70 void dfs(int cnt, int s) {
 71     if (s > sum - s || cnt >= ans || !is_connect(cnt)) return;  // 剪支
 72     if (s == sum - s) {
 73         ans = cnt;
 74         return;
 75     }
 76     
 77     // 遍历集合中的每一个格子
 78     for (int i = 0; i < cnt; i++) {
 79         int x = a[i].first, y = a[i].second;
 80         for (int j = 0; j < 4; j++) {
 81             int xx = x + dx[j], yy = y + dy[j];
 82             if (xx >= 0 && xx < n && yy >= 0 && yy < m && !vis[xx][yy]) {
 83                 a[cnt] = {xx, yy};
 84                 if (is_exist(cnt + 1)) continue;    // 已经搜过的情况就不再搜了
 85                 
 86                 vis[xx][yy] = true;
 87                 dfs(cnt + 1, s + graph[xx][yy]);
 88                 vis[xx][yy] = false;
 89             }
 90         }
 91     }
 92 }
 93 
 94 int main() {
 95     scanf("%d %d", &m, &n);
 96     for (int i = 0; i < n; i++) {
 97         for (int j = 0; j < m; j++) {
 98             scanf("%d", &graph[i][j]);
 99             sum += graph[i][j];
100         }
101     }
102     
103     if (sum % 2 == 0) {
104         vis[0][0] = true;
105         a[0] = {0, 0};
106         dfs(1, graph[0][0]);
107     }
108     
109     printf("%d", ans == N * N ? 0 : ans);
110     
111     return 0;
112 }

 

参考资料

  AcWing 1206. 剪格子(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/802/

posted @ 2022-03-17 12:14  onlyblues  阅读(169)  评论(0编辑  收藏  举报
Web Analytics