校内 C218
T2 与 T4 蛮有意思的,写一下。
T2
题目描述:
已知两个序列 \(a\) 与 \(b\),可以进行一次操作:将其中任意一个序列的一个区间翻转。求这样的 \(\sum{a_i \times b_i}\) 的最大值。长度 <= 5000;
解析
我们考虑 DP。设 \(f[i][j]\) 表示翻转两个序列的任意一个序列的区间,求得的最大值。目标:\(\max{f[i][j]}\)。
考虑如何转移,我们先假设答案只用翻转 \(a\)。
枚举每一个数作为区间的中点,那么分两种情况:
-
区间大小为奇数,那么我们只用枚举 \(i\) 作为中点,与他到区间左右端点的长度,设为 \(j\)。那么从 \(f[i - j + 1][i + j - 1]\) 转移到 \(f[i - j][i + j]\) 也就相当于多将
swap(a[i - j], a[i + j])
,所需的花费,就应该是 \(a[i + j] \times b[i - j] + a[i - j] \times b[i + j] - a[i + j] \times b[i + j] - a[i - j] \times b[i - j]\)。那么即为 \(f[i - j][i + j] = f[i - j + 1][i + j - 1] + a[i + j] \times b[i - j] + a[i - j] \times b[i + j] - a[i + j] \times b[i + j] - a[i - j] \times b[i - j]\),初值即使 \(f[i][i] = sum\)。 -
区间大小为偶数,那么中点有两个 \(i\) 与 \(i + 1\),同上面的思想。初值为 \(f[i][i + 1] = sum - a[i] * b[i] - a[i + 1] * b[i + 1] + a[i] * b[i + 1] + a[i + 1] * b[i];\)。
而因为我们只是假设翻转 \(a\),所以我们再做一次 DP 翻转 \(b\),答案取最大值即可。
#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N = 5500;
int n, a[N], b[N], f[N][N], sum, ans;
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i], sum += a[i] * b[i];
ans = sum;
for (int i = 1; i <= n; i++) {
f[i][i] = sum;
f[i][i + 1] = sum - a[i] * b[i] - a[i + 1] * b[i + 1] + a[i] * b[i + 1] + a[i + 1] * b[i];
for (int j = 1; j + i <= n && i - j; j++) f[i - j][i + j] = f[i - j + 1][i + j - 1] - a[i - j] * b[i - j] - a[i + j] * b[i + j] + a[i + j] * b[i - j] + a[i - j] * b[i + j], ans = max(ans, f[i - j][i + j]);
for (int j = 1; j + i + 1 <= n && i - j; j++) f[i - j][i + j + 1] = f[i - j + 1][i + j] - a[i - j] * b[i - j] - a[i + j + 1] * b[i + j + 1] + a[i + j + 1] * b[i - j] + a[i - j] * b[i + j + 1], ans = max(ans, f[i - j][i + j + 1]);
}
cout << ans;
return 0;
}
T4
题目描述
压缩长度为 \(n \le 5000\) 由 \(1 \sim k \le 998244353\) 种字符组成的字符串。求出压缩后长度 \(< n\) 的种类,对 \(998244353\) 取模。
解析
DP。设 \(f[i][j]\) 表示前 \(i\) 位压缩后成为 \(j\) 的个数。
首先考虑 \(i \le 9\) 的情况。\(f[i][j] += f[i - 1][j] + f[i - 1][j - 2] \times (k - 1)\)。其中 \(f[i - 1][j]\) 表示当前的字符与上一个相同的情况,因为 \(i \le 9\) 所以最多是形如 \(a9\) 这样的,还是 \(2\) 位,\(j\) 不变。\(f[i - 1][j - 2] \times (k - 1)\) 表示与上一位不同,也很好理解,因为与上一位不同就要多出 \(2\) 个字符,而且当前字符有 \(k - 1\) 种情况,所以是 \(j - 2\)。那么初值即为 \(f[1][2] = k\)。
\(10 \le i \le 99\)。首先肯定会有 \(f[i][j] += f[i - 1][j] + f[i - 1][j - 2] \times (k - 1)\) 这个方程。单这只是每次增添两位。我们考虑拎出来 \(i - 9 \sim i\) 出来与 \(i - 10\) 不同。这样我们就会形成形如 \(a10\) 的 \(3\) 位字符串。那么我们就又加上了 \(f[i - 10][j - 3] \times (k - 1)\)。但这就对了吗?显然不是,因为加的 \(f[i - 1][j]\) 包含了 \(i - 9 \sim 1\) 位为开头的情况,也就是 \(a10\) 但在程序中他会既属于压缩后为 \(2\) 位又是压缩后为 \(3\) 位的字符串,所以我们要减去 \(i\) 位与 \(i - 9 \sim 1\) 相同的可能。也就是减去 \(f[i - 10][j - 2] \times (k - 1)\),因为是第 \(i - 9\) 位与 \(i - 10\) 位不同的情况,这样连起来刚好 \(10\) 个。刚好不合法。初始化 \(f[10][3] = k\)。
剩下的同上。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5005, mod = 998244353;
int n, f[N][N], k, ans, s[N], sum[N][N];
signed main() {
ios::sync_with_stdio(0);
cin >> n >> k;
f[1][2] = k; f[10][3] = k; f[100][4] = k; f[1000][5] = k;
for (int i = 1; i <= 9; i++) s[i] = 2;
for (int i = 10; i <= 99; i++) s[i] = 3;
for (int i = 100; i <= 999; i++) s[i] = 4;
for (int i = 1000; i <= 5000; i++) s[i] = 5;
for (int i = 2; i <= n; i++) {
for (int j = s[i]; j <= n; j++) {
f[i][j] += f[i - 1][j - 2] * (k - 1);
f[i][j] %= mod;
f[i][j] += f[i - 1][j];
f[i][j] %= mod;
if (i >= 10) f[i][j] -= f[i - 10][j - 2] * (k - 1);
f[i][j] += mod;
f[i][j] %= mod;
if (i >= 100) f[i][j] -= f[i - 100][j - 3] * (k - 1);
f[i][j] += mod;
f[i][j] %= mod;
if (i >= 1000) f[i][j] -= f[i - 1000][j - 4] * (k - 1);
f[i][j] += mod;
f[i][j] %= mod;
if (i >= 10) f[i][j] += f[i - 10][j - 3] * (k - 1);
f[i][j] %= mod;
if (i >= 100) f[i][j] += f[i - 100][j - 4] * (k - 1);
f[i][j] %= mod;
if (i >= 1000) f[i][j] += f[i - 1000][j - 5] * (k - 1);
f[i][j] %= mod;
}
}
for (int i = 1; i < n; i++) {
ans += f[n][i];
ans %= mod;
}
cout << ans << endl;
return 0;
}