2022 ICPC Hangzhou
A
令初始序列的和为 \(sum\),则最后的和是 \(sum + ns + {n(n + 1) \over 2}d\),我们令 \(f(x) = (sum + x) \% m, x \in [0, m)\),显然这是一个分段函数,令 \(x = y\) 时,\(f(x) = 0\)。当 \(x = ns + {n(n + 1) \over 2}d\) 时,\(x\) 能取到值只能是 \((k \times gcd(n, {n(n + 1) \over 2})) \% m\),于是有 \(k\times gcd(n, {n(n + 1) \over 2}) = t\times m + x\),即 \(k\times gcd(n, {n(n + 1) \over 2}) + (-t) \times m = x\)。由拓展欧几里得我们可以解出一组使得 \(x\) 最小的 \(k\) 和 \(-t\),然后看看这个最小的 \(x\) 能否通过乘上一个系数来成为大于等于 \(y\) 的一个数,如果能让解得的 \(k\) 乘上这个系数。得到 \(k\) 之后,就是要解 \(ns + {n(n + 1) \over 2}d = k\times gcd(n, {n(n + 1) \over 2})\),由拓展欧几里得解就好了。
int extend_gcd(i64 a, i64 b, i64 &x, i64 &y) {if (b == 0) {x = 1, y = 0;return a;}int res = extend_gcd(b, a % b, x, y);int t = x;x = y;y = t - (a / b) * y;return res;
}std::pair<i64, i64> getpair(i64 a, i64 b) {i64 x = 0, y = 0;extend_gcd(a, b, x, y);return { x, y };
}void solve() {int n = 0;i64 m = 0;std::cin >> n >> m;i64 sum = 0;for (int i = 0; i < n; ++i) {int a = 0;std::cin >> a;sum += a;}sum %= m;i64 s = 0, d = 0;i64 g = extend_gcd(1ll * n, 1ll * n * (n + 1) / 2, s, d);i64 k = getpair(g, m).first % m;if(k < 0) {k += m;}i64 t = (((m - sum - 1) % m) / std::__gcd(g, m) + 1) % m;if (t * std::__gcd(g, m) < m) {(k *= t) %= m;}s %= m;d %= m;(s *= k) %= m;(d *= k) %= m;if (s < 0) {s += m;}if (d < 0) {d += m;}i64 ans = (sum + k * g) % m;std::cout << ans << '\n';std::cout << s << ' ' << d << '\n';return;
}
C
进行阅读理解后,可以将题目转化为:有 \(n\) 个物品,每个物品有体积和价值,要求选择若干物品放入容量为 \(k\) 的背包,特别的,当最后一件物品不能放入背包时,可以将该物品的体积减小至恰好填满背包,同时价值变化为对应的价值。
于是设计 \(f_{i, j, 0/1}\) 表示前 \(i\) 件物品,占 \(j\) 体积且 0(还未)/1(已经) 确定了那件减小体积的物品时,价值的最大值。
转移是 trivial 的。但是要注意初始化的问题,需要初始化为 \(-Inf\)。并且,由于可能所有物品都填不满背包,所以答案 \(f[n][k][1]\) 要跟 \(max_j\{f[n][j][0]\}\) 取最大值。并且注意不能跟 \(max_j\{f[n][j][1]\}\) 取最大值,因为当有物品被缩小体积时,背包一定能被填满,\(j < k\) 的情况都是不合法的。
int n, m;
i64 f[K + 5][2], g[K + 5];
int p[N + 5][P + 5];i64 cnt(i64 s) {for (int i = 0; i <= s; ++i) {for (int j = 0; j < 2; ++j) {f[i][j] = -Inf;}}f[0][0] = 0;i64 res = 0ll;for (int i = 1; i <= n; ++i) {for (int j = 0; j <= s; ++j) {g[j] = f[j][1];}int w = 0;i64 v = 0ll;for (w = 1; w <= p[i][0]; ++w) {v = p[i][w];for (int j = s; j >= w; --j) {f[j][1] = std::max(f[j][1], f[j - w][0] + v);}}w = p[i][0];v = p[i][w];for (int j = s; j >= w; --j) {f[j][0] = std::max(f[j][0], f[j - w][0] + v);f[j][1] = std::max(f[j][1], g[j - w] + v);res = std::max(res, f[j][0]);}}return std::max(res, f[m][1]);
}void solve() {std::cin >> n >> m;for (int i = 1; i <= n; ++i) {std::cin >> p[i][0];for (int j = 1; j <= p[i][0]; ++j) {std::cin >> p[i][j];}}std::cout << cnt(m) << '\n';
}
K
对于每两个字符串(较短串是较长串的前缀除外),我们记录下第一个字符不相同的位置,并记录下该位置的两个字母。给出字母表时查一下就好了。比如两个字符串 ab ac,则将 b c 记录下来(cnt[b][c]++),当给出字母表时,若在字母表中 c 在 b 前面,则 ans += cnt[b][c]。处理出 \(cnt\) 可以用字典树,对于一个串是另一个串的前缀的情况,在插入字典树是单独处理就好了。