呜呜呜被这道题干翻饮恨西北了。
题面
有一个 N×NN\times NN×N 网格。让单元格 (i,j)(i,j)(i,j) 表示从上往下第 iii 行和从左往上第 jjj 列中的单元格。每个单元格包含一个从 "1"到 "9"的数字;单元格 (i,j)(i,j)(i,j) 包含 Ai,jA_{i,j}Ai,j 。
最初,一个标记位于 (1,1)(1,1)(1,1) 单元。设 SSS 为空字符串。重复以下操作 2N−12N-12N−1 次:
- 将当前单元格中的数字追加到 SSS 的末尾。
- 将标记向下或向右移动一格。但不要在 (2N−1)(2N-1)(2N−1) 操作中移动标记。
经过 2N−12N-12N−1 次操作后,标记位于 (N,N)(N,N)(N,N) 单元格, SSS 的长度为 2N−12N-12N−1 。
将 SSS 解释为整数。分数就是这个整数除以 MMM 的余数。
求可达到的最大得分。
思路
根据位值原理,我们可以假设单元格 (i,j)(i, j)(i,j) 上写的是整数 Ai,j⋅102N−i−jA_{i,j} \cdot 10^{2N - i - j}Ai,j⋅102N−i−j 而不是数字 Ai,jA_{i,j}Ai,j,那么得分就变成了路径上每个单元格上所写整数的总和对 MMM 取模的结果。
注意到数据范围内的 NNN 并不大,考虑从左上角向右下角进行搜索呢。
答案是不行,因为路径总长 2N−12N - 12N−1,其中大部分操作有两种选择(向右或向下),总时间复杂度为 O(22N−1)O(2^{2N - 1})O(22N−1),这显然是不可行的。
但是!!我们可以发现这样子开根号之后可行!
2N−12N - 12N−1 最大是 393939,这其实是一个提示点。
当我们发现一个可以进行搜索的问题的时间复杂度中指数为 30∼4030 \sim 4030∼40 时,就应该考虑下能否使用 meet - in - the - middle,因为 O(2N)O(2^N)O(2N) 的时间复杂度是可以接受的。
考虑能否使用 meet - in - the - middle 求解:我们可以将路径以副对角线为中点分为两部分。
对于副对角线上所有点,记录从左上角出发到该点之前能够组成的值,以及从右下角出发到该点之前能够组成的值。
这样对于某副对角线上的点,即可通过某前半部分的和 + 该点的值 + 某后半部分的和来得到整个路径。
接下来,对于每个副对角线上的点,设该点上的值为 kkk,其某前半部分的和为 xix_ixi,某后半部分的值为 yjy_jyj,问题转化为求 xi+k+yjx_i + k + y_jxi+k+yj 的最大值。
我们可以枚举 xix_ixi,此时 xix_ixi 与 kkk 都是已知的,只需要确定 yjy_jyj 的值即可。
因为需要对 MMM 取模,所以答案 xi+k+yjx_i + k + y_jxi+k+yj 的最大可能值为 M−1M - 1M−1,故我们只需要找到小于 M−xi−kM - x_i - kM−xi−k 的最大 yjy_jyj 即可(若找不到即为 yyy 中最大值)。
这里我一开始想的是三分(学高级算法学傻了),10s 后才发现这可以通过对 yyy 进行排序后二分查找来实现。
这样即可将时间复杂度降为 O(2Nlog2N)O(2^{N} \log 2^{N})O(2Nlog2N)。可以通过!
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 21;
int pow10[2 * N];
int a[N][N];
vector<int> v1[N], v2[N];
void dfs1(int x, int y, int sum, int n, int m) {
if (x + y == n + 1) {
v1[x].push_back(sum);
return;
}
if (x + 1 <= n)
dfs1(x + 1, y, (sum + a[x][y]) % m, n, m);
if (y + 1 <= n)
dfs1(x, y + 1, (sum + a[x][y]) % m, n, m);
}
void dfs2(int x, int y, int sum, int n, int m) {
if (x + y == n + 1) {
v2[x].push_back(sum);
return;
}
if (x - 1 >= 1)
dfs2(x - 1, y, (sum + a[x][y]) % m, n, m);
if (y - 1 >= 1)
dfs2(x, y - 1, (sum + a[x][y]) % m, n, m);
}
signed main() {
int n, m;
cin >> n >> m;
pow10[0] = 1;
for (int i = 1; i <= 2 * n; i++)
pow10[i] = (pow10[i - 1] * 10) % m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
a[i][j] = a[i][j] * pow10[2 * n - i - j] % m;
}
}
dfs1(1, 1, 0, n, m);
dfs2(n, n, 0, n, m);
for (int i = 1; i <= n; i++) {
sort(v1[i].begin(), v1[i].end());
sort(v2[i].begin(), v2[i].end());
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int val : v1[i]) {
int sum = (val + a[i][n - i + 1]) % m;
auto it = lower_bound(v2[i].begin(), v2[i].end(), m - sum);
if (it != v2[i].begin())
ans = max(ans, (sum + * (it - 1)) % m);
ans = max(ans, (sum + v2[i].back()) % m);
}
}
cout << ans << '\n';
return 0;
}