P3974 [TJOI2015] 组合数学 题解

Description

给一个网格图,其中某些格子有一些财宝。每次从左上角出发,只能往右或下走,每一次经过一个格子至多只能捡走一块财宝,至少要走几次才可能把财宝全捡完?

\(1\leq n \leq 1000\)\(1\leq m \leq 1000\),每个格子中的财宝不超过 \(10^6\) 块。

Solution

考虑把每个点 \((i,j)\) 拆成 \(a_{i,j}\) 个点,然后题目相当于要求出这个网格图的最小链覆盖。

由于两个点互相不可达一定是一个在左下,一个在右上,或者在同一位置。所以根据 Dilworth 定理,题目就是求一个从右上到左下的坐标单调的路径使得 \(a\) 之和最大。

直接 dp 即可。

时间复杂度:\(O(nm)\)

Code

#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 1e3 + 5;

int n, m;
int a[kMaxN][kMaxN], f[kMaxN][kMaxN];

void dickdreamer() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= m; ++j)
      std::cin >> a[i][j];
  for (int i = 0; i <= n + 1; ++i)
    for (int j = 0; j <= m + 1; ++j)
      f[i][j] = 0;
  f[0][m + 1] = 0;
  for (int i = 1; i <= n; ++i)
    for (int j = m; j; --j)
      f[i][j] = std::max({f[i - 1][j + 1] + a[i][j], f[i - 1][j], f[i][j + 1]});
  std::cout << f[n][1] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2024-06-24 09:14  下蛋爷  阅读(13)  评论(0)    收藏  举报