CZOI Round 3
因为你 生来就形只影单
才将呼救向世界呐喊
剥离鲜明色彩隐没在 声浪层叠
我们是否等来许诺的晴天
曾妄想借音乐唤回的温暖
蒙头奔跑吗 明天
寻回纯粹吧 尽管孑然
CZOI Round 3
可爱棉羊
最小值是相互传染,答案是 \(x\),但 \(x = 1\) 时需要单独传染一个,答案是 \(2\)。
一个被感染的小羊多轮传染形似向两边染色,第一轮只能染一边,故一个被感染的小羊的贡献为 \(2t\),答案为 \(\min(2xt,n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
x = 0; int f = 1; char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= f; read(arg...);
}
void write() {}
template<typename T> void write(T x) {
if (x < 0) { putchar('-'), x = -x; }
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
int n, t, x;
int main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
read(n, t, x);
i64 Min = x == 1 ? 2 : x, Max = min(i64(n), i64(2) * t * x);
write(Max); putchar(' '); write(Min);
return 0;
}
// START AT 2025 / 05 / 03 14 : 33 : 19
星光闪耀
首先观察出题目求
根据等比数列求和将最后一项展开,可得
\(\dfrac{\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}k^{i_{m - 1} + 1}}{k - 1}\)可以按上面化简。递归处理即可。考虑求 \(\sum\limits_{i_1 = 1}^n\sum\limits_{i_2 = 1}^{i_1}\cdots\sum\limits_{i_p = 1}^{i_{p - 1}}1\)。
这相当于求单调不增的 \(\{i_p\}\) 的个数,定义 \(a_j\) 表示 \(j\) 在 \(\{i_p\}\) 中出现的次数,则转化为求 \(\{a_n\}\),满足 \(\sum\limits_{i = 1}^na_i = p\)。
相当于有 \(p\) 个元素,要求分成 \(n\) 组,每组允许为空。
能用插板法解决,答案是 \(\begin{pmatrix}p + n - 1\\n - 1\end{pmatrix}=\begin{pmatrix} n + p - 1\\ p \\ \end{pmatrix}\)。
故
因为出题人说会卡常所以要用递推实现
展开 \(m\) 项后得到 \(\dfrac{k^{n + m} - k^m}{k - 1}\),之后递推。
时间复杂度 \(\mathcal{O}(N+\sum m)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define int long long
void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
x = 0; int f = 1; char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= f; read(arg...);
}
void write() {}
template<typename T> void write(T x) {
if (x < 0) { putchar('-'), x = -x; }
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
const int mod = 998244353;
const int N = 4e6 + 5;
int fac[N], inv[N], ifac[N];
int T, n, m, k;
int Pow(int a, int b) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = 1LL * res * a % mod;
a = 1LL * a * a % mod;
}
return res;
}
int binom(int n, int m) {
return 1LL * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
void solve() {
read(n, m, k);
if (k == 1) {
write(binom(n + m - 1, m)); putchar('\n');
} else {
int inv1 = Pow(k - 1, mod - 2), inv2 = Pow(k, mod - 2), ans = Pow(k, n + m);
k = Pow(k, m);
for (int i = 1; i <= m; i++) {
ans = (1LL * ans - 1LL * binom(n + i - 2, i - 1) * k % mod + mod) % mod * inv1 % mod;
k = 1LL * k * inv2 % mod;
}
write(ans); putchar('\n');
}
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
fac[0] = ifac[0] = inv[1] = 1;
for (int i = 2; i <= N - 5; i++) inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod;
for (int i = 1; i <= N - 5; i++)
fac[i] = 1LL * fac[i - 1] * i % mod,
ifac[i] = 1LL * ifac[i - 1] * inv[i] % mod;
read(T);
while (T--) solve();
return 0;
}
// START AT 2025 / 05 / 03 22 : 35 : 12
消除序列
显然,令 \(a\) 中元素变 \(0\) 的顺序仅与 \(b\) 相关。只需要考虑怎么移。
显然不能移动 \(a\) 排列,用指针 \(h\) 表示 \(a_1\) 所在的位置就好。
考虑动态规划,除了交换 \(x, y\) 对后面有影响,别的都没有,单独开一维,定义 \(f(i, 0/1)\) 表示处理了原 \(a_i\) 有/没有换 \(x, y\) 的最小代价。
定义 \(L(i)\) 表示从 \(h\) 右移到 \(b_i\) 在 \(a\) 当前位置需要的有代价步数。\(R(i)\) 表示从 \(h\) 左移到 \(b_i\) 在 \(a\) 当前位置需要的有代价步数
则
所以求出 \(L(i), R(i)\) 就解决问题了。
有代价步数是 \(h\) 移动路径上 \(a_h \not= 0\) 的个数。这用线段树,树状数组之类的很好做。
时间复杂度是 \(\mathcal{O}(n \log n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
x = 0; int f = 1; char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= f; read(arg...);
}
void write() {}
template<typename T> void write(T x) {
if (x < 0) { putchar('-'), x = -x; }
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
const int N = 1e6 + 5;
#define lowbit(x) (x & (-x))
int n, x, y, z, tr[N], head = 1;
int a[N], b[N], c[N];
i64 f[N][2];
void add(int u, int d) {
for (; u <= n; u += lowbit(u)) tr[u] += d;
}
int query(int u) {
int res = 0;
for (; u; u -= lowbit(u)) res += tr[u];
return res;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
read(n, x, y, z);
for (int i = 1; i <= n; i++) add(i, 1);
for (int i = 1; i <= n; i++) {
read(a[i]); c[a[i]] = i;
}
for (int i = 1; i <= n; i++) read(b[i]);
f[0][1] = 1e18;
for (int i = 1; i <= n; i++) {
int left, right;
if (head <= c[b[i]]) {
left = query(c[b[i]] - 1) - query(head - 1);
right = query(head) + query(n) - query(c[b[i]]);
} else {
left = query(c[b[i]] - 1) + query(n) - query(head - 1);
right = query(head) - query(c[b[i]]);
}
f[i][0] = min(f[i - 1][0] + 1LL * left * x, f[i - 1][0] + 1LL * right * y);
f[i][0] = min(f[i][0], min(f[i - 1][1] + 1LL * left * x + z, f[i - 1][1] + 1LL * right * y + z));
f[i][1] = min(f[i - 1][0] + 1LL * left * y + z, f[i - 1][0] + 1LL * right * x + z);
f[i][1] = min(f[i][1], min(f[i - 1][1] + 1LL * left * y, f[i - 1][1] + 1LL * right * x));
add(c[b[i]], -1);
head = c[b[i]];
}
write(min(f[n][0], f[n][1]));
return 0;
}
// START AT 2025 / 05 / 11 22 : 37 : 23
数字变换
定义 \(f(i, j)\) 表示第 \(i\) 次令 \(a = j\) 的最小值。由题意
考虑通过 \(x_l\) 简化转移。定义 \(g\)。
则
接着化简,令 \(q = p \operatorname{AND} x_j\),则 \(q \subset p,q\sub x_j\)。考虑枚举 \(q\)。
则
可以先用高维后缀 \(\min\),求出 \(\min_{p \supset q}\{a_p - 2q\}\),存到 \(a_q\) 中。
然后用高维前缀 \(\min\) 求出 \(\min_{q \sub x_j} {a_p}\) 存到 \(a_p\) 中。转移就做完了。
时间复杂度是 \(\mathcal{O}(kV\log V)\)。(\(V\) 为 \(x\) 值域)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
x = 0; int f = 1; char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
x *= f; read(arg...);
}
void write() {}
template<typename T> void write(T x) {
if (x < 0) { putchar('-'), x = -x; }
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5 + 5;
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
int n, p, k, w[N][11], f[15][N], a[15][(1 << 16) + 5];
int c, y[65536], x[N], L;
unsigned long long seed;
int get_rand(int mod) {
seed ^= seed << 14;
seed ^= seed >> 7;
seed ^= seed << 19;
seed ^= seed << 23;
return seed % mod;
}
void get_input() {
rep(i, 1, n) x[i] = y[get_rand(c)];
rep(i, 1, n) rep(j, 1, k) w[i][j] = get_rand(1000000);
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("code.in", "r", stdin);
freopen("code.out", "w", stdout);
#endif
read(n, p, k, c, seed);
rep(i, 0, c - 1) read(y[i]);
get_input();
rep(i, 1, n) L = max(L, x[i]);
memset(f, 0x3f, sizeof(f));
memset(a, 0x3f, sizeof(a));
f[0][p] = 0;
rep(i, 1, k) {
rep(j, 1, n) a[i][x[j]] = min(a[i][x[j]], f[i - 1][j]);
rep(j, 0, 15) rep(l, 0, (1 << 16) - 1)
if (!(l >> j & 1)) a[i][l] =min(a[i][l], a[i][l | (1 << j)]);
rep(j, 0, (1 << 16) - 1) a[i][j] -= 2 * j;
rep(j, 0, 15) rep(l, 0, (1 << 16) - 1)
if (l >> j & 1) a[i][l] = min(a[i][l], a[i][l ^ (1 << j)]);
rep(j, 1, n) f[i][j] = w[j][i] + a[i][x[j]] + 2 * L;
}
rep(i, 1, n) write(f[k][i]), putchar(' ');
return 0;
}
// START AT 2025 / 06 / 15 20 : 41 : 28
总结
T1 < T3 < T4 = T2
T1,T3是可做题,下次看到容易用数据结构解决的问题要快点。需要练数据结构优化 dp。
T2 很困难倒也说不上,但是一开始就想错了,然后死磕。为什么我总是死磕最难题
需要练习组合数学,多做思维题。
T4感觉是很好的题。以后做 dp 可以尝试不断对转移方程化简。
看到位运算,dp 可以往高维前缀和想。
补题间隔太久远了。虽然中间发生了一些问题吧……以后不会了。
尽管环境不好,但还是希望我能像摘要里写的一样
寻回纯粹吧 尽管孑然
浙公网安备 33010602011771号