ABC F 题解

呜呜呜被这道题干翻饮恨西北了。

题面

有一个 N × N N\times N N×N 网格。让单元格 ( i , j ) (i,j) (i,j) 表示从上往下第 i i i 行和从左往上第 j j j 列中的单元格。每个单元格包含一个从 "1"到 "9"的数字;单元格 ( i , j ) (i,j) (i,j) 包含 A i , j A_{i,j} Ai,j

最初,一个标记位于 ( 1 , 1 ) (1,1) (1,1) 单元。设 S S S 为空字符串。重复以下操作 2 N − 1 2N-1 2N1 次:

  • 将当前单元格中的数字追加到 S S S 的末尾。
  • 将标记向下或向右移动一格。但不要在 ( 2 N − 1 ) (2N-1) (2N1) 操作中移动标记。

经过 2 N − 1 2N-1 2N1 次操作后,标记位于 ( N , N ) (N,N) (N,N) 单元格, S S S 的长度为 2 N − 1 2N-1 2N1

S S S 解释为整数。分数就是这个整数除以 M M M 的余数。

求可达到的最大得分。

思路

根据位值原理,我们可以假设单元格 ( i , j ) (i, j) (i,j) 上写的是整数 A i , j ⋅ 1 0 2 N − i − j A_{i,j} \cdot 10^{2N - i - j} Ai,j102Nij 而不是数字 A i , j A_{i,j} Ai,j,那么得分就变成了路径上每个单元格上所写整数的总和对 M M M 取模的结果。

注意到数据范围内的 N N N 并不大,考虑从左上角向右下角进行搜索呢。

答案是不行,因为路径总长 2 N − 1 2N - 1 2N1,其中大部分操作有两种选择(向右或向下),总时间复杂度为 O ( 2 2 N − 1 ) O(2^{2N - 1}) O(22N1),这显然是不可行的。

但是!!我们可以发现这样子开根号之后可行!

2 N − 1 2N - 1 2N1 最大是 39 39 39,这其实是一个提示点。

当我们发现一个可以进行搜索的问题的时间复杂度中指数为 30 ∼ 40 30 \sim 40 3040 时,就应该考虑下能否使用 meet - in - the - middle,因为 O ( 2 N ) O(2^N) O(2N) 的时间复杂度是可以接受的。

考虑能否使用 meet - in - the - middle 求解:我们可以将路径以副对角线为中点分为两部分。

对于副对角线上所有点,记录从左上角出发到该点之前能够组成的值,以及从右下角出发到该点之前能够组成的值。

这样对于某副对角线上的点,即可通过某前半部分的和 + 该点的值 + 某后半部分的和来得到整个路径。

接下来,对于每个副对角线上的点,设该点上的值为 k k k,其某前半部分的和为 x i x_i xi,某后半部分的值为 y j y_j yj,问题转化为求 x i + k + y j x_i + k + y_j xi+k+yj 的最大值。

我们可以枚举 x i x_i xi,此时 x i x_i xi k k k 都是已知的,只需要确定 y j y_j yj 的值即可。

因为需要对 M M M 取模,所以答案 x i + k + y j x_i + k + y_j xi+k+yj 的最大可能值为 M − 1 M - 1 M1,故我们只需要找到小于 M − x i − k M - x_i - k Mxik 的最大 y j y_j yj 即可(若找不到即为 y y y 中最大值)。

这里我一开始想的是三分(学高级算法学傻了),10s 后才发现这可以通过对 y y y 进行排序后二分查找来实现。

这样即可将时间复杂度降为 O ( 2 N log ⁡ 2 N ) O(2^{N} \log 2^{N}) O(2Nlog2N)。可以通过!

#include 
#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;
}

你可能感兴趣的:(c++,算法)