2022 ICPC Hangzhou
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\) 可以用字典树,对于一个串是另一个串的前缀的情况,在插入字典树是单独处理就好了。
浙公网安备 33010602011771号