ABC F 题解

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

题面

有一个 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-12N1 次:

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

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

SSS 解释为整数。分数就是这个整数除以 MMM 的余数。

求可达到的最大得分。

思路

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

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

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

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

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

当我们发现一个可以进行搜索的问题的时间复杂度中指数为 30∼4030 \sim 403040 时,就应该考虑下能否使用 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_ixikkk 都是已知的,只需要确定 yjy_jyj 的值即可。

因为需要对 MMM 取模,所以答案 xi+k+yjx_i + k + y_jxi+k+yj 的最大可能值为 M−1M - 1M1,故我们只需要找到小于 M−xi−kM - x_i - kMxik 的最大 yjy_jyj 即可(若找不到即为 yyy 中最大值)。

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

这样即可将时间复杂度降为 O(2Nlog⁡2N)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值