joi2022_yo2_c 国土分割 (Land Division) 题解

国土分割 (Land Division)

推销我的洛谷博客。

题意

给定一个 \(n \times m\) 的矩阵 \(a\),你需要选择在横向或纵向分割至少一次,使得每个分割出来的小矩阵的 \(a_{i,j}\) 之和相等。

数据范围

  • \(1 \leqslant n,m \leqslant 50\)
  • \(1 \leqslant a_{i, j} \leqslant 10^5\)

思路

闲话

老师给我们复现了一下,场切了,心路历程如下。

第一眼:好像有些难度啊。

第二眼:\(1\leqslant n,m \leqslant 50\),这不是水题吗。

第三眼:欸如果 \(a_{i,j}=0\) 怎么办。

第四眼:哦 \(a_{i,j} \geqslant 1\),那没事了。

正题

看起来挺吓唬人,但数据范围太小,所以考虑枚举。

枚举最左上角的小矩阵的大小,假设这个矩阵的右下角为 \((x,y)\),那么我们就知道了每个矩阵的 \(a_{i,j}\) 之和。

那么对于列的分割方法,你可以通过前 \(x\) 行的信息推出要在哪些列进行分割。同理,你可以推出要在哪些行进行分割,判断是否合法即可。

  • 对于一组 \((x,y)\) 有几种分割方式呢?正如闲话中所说的,\(a_{i,j}\) 都为正数,那么可以发现对于每一组 \((x,y)\)至多有一种分割方案。

  • 对于求一个矩阵的 \(a_{i,j}\) 之和,不难想到二维前缀和优化。

详细见代码。

复杂度

  • 时间:\(O(n^2\times m^2)\)
  • 空间:\(O(n\times m)\)

Code

点击查看代码
#include <bits/stdc++.h>

using namespace std;

int n, m, a[55][55], sum[55][55], cnt, stk[55], top, ans;

int C (int x, int y, int a, int b) { // 二维前缀和求解矩阵和
  return sum[a][b] - sum[x - 1][b] - sum[a][y - 1] + sum[x - 1][y - 1];
}

void Solve (int i, int j) { // 对于一组 (i, j),判断是否具有合法的分割方案
  cnt = C(1, 1, i, j), top = 1, stk[1] = j; // cnt 为每个矩阵的和,stk 用于记录在哪些列需要分割
  for (int k = j + 1; k <= m; k++) { // 利用前 i 行推出在哪些列需要分割
    if (C(1, stk[top] + 1, i, k) == cnt) { // 找出来了一个矩阵
      stk[++top] = k;
    } else if (C(1, stk[top] + 1, i, k) > cnt) { // 不合法
      return ;
    }
  }
  if (stk[top] != m) { // 最右上角的矩阵和没有达到 cnt
    return ;
  }
  int lst = i;
  for (int k = i + 1; k <= n; k++) { // 已知在哪些列需要分割,推在哪些行需要分割
    bool f = 0;
    for (int l = 1; l <= top; l++) {
      if (C(lst + 1, stk[l - 1] + 1, k, stk[l]) < cnt) { // 这一行还不够
        f = 1;
      } else if (C(lst + 1, stk[l - 1] + 1, k, stk[l]) > cnt) { // 这一行有矩阵和已经超过了 cnt,不可能合法
        return ;
      }
    }
    if (!f) { // 找到了满足要求的一行
      lst = k;
    }
  }
  if (lst != n) { // 最下方的矩阵还不合法
    return ;
  }
  // cout << i << ' ' << j << '\n';
  ans++;
}

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  //freopen("div.in", "r", stdin);
  //freopen("div.out", "w", stdout);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      cin >> a[i][j], sum[i][j] = sum[i][j - 1] + a[i][j];
    }
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      sum[i][j] += sum[i - 1][j]; // 逐维前缀和
    }
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      Solve(i, j);
    }
  }
  cout << ans - 1; // 由于题目要求至少分割一次,则需要减去 (n, m) 这一组
  return 0;
}
posted @ 2024-07-09 18:24  wnsyou  阅读(19)  评论(0编辑  收藏  举报