[CF1942] CodeTON Round 8 (Div. 1 + Div. 2, Rated, Prizes! A~E 題解
A. Farmer John's Challenge
只有兩種情況,一種是單調遞增,這時 \(k = 1\),另一種是所有數都相同,這時 \(k = n\)。
B. Bessie and MEX
首位可以確定,然後從前往後增量構造 \(p\) 即可。
void work() {
cin >> n;
s.clear();
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 0; i < n; i ++)
s.insert(i), st[i] = 0;
if(a[1] == 1) p[1] = 0;
else p[1] = -a[1];
s.erase(p[1]), st[p[1]] = 1;
for(int i = 2; i <= n; i ++) {
int mex = *s.begin();
if(mex - a[i] >= 0 && mex - a[i] < n && !st[mex - a[i]]) {
p[i] = mex - a[i];
}
else {
p[i] = mex;
}
st[p[i]] = 1, s.erase(p[i]);
}
for(int i = 1; i <= n; i ++)
cout << p[i] << ' '; cout << '\n';
return ;
}
C1. Bessie's Birthday Cake (Easy Version)
注意到相鄰連續的一段特殊點可以被縮掉,可以這樣連邊:
然後這一段點就只剩下左右端點了,最後會得到若干個特殊點,這樣從一個點向其它所有點連邊,然後把邊上的邊連起來,這樣就是最優解了。
C2. Bessie's Birthday Cake (Hard Version)
考慮在哪加入特殊點會帶來貢獻。
只有最後連邊的時候,相鄰特殊點中間可以加入新貢獻。
每次加入一個點會有兩種貢獻,分別是 2/3。
對於 3 的貢獻可以按凸包大小排序,然後對於 2 的直接放到最後。
int n, x[N], m, y, d[N], idx;
void work() {
cin >> n >> m >> y;
idx = 0;
for(int i = 1; i <= m; i ++)
cin >> x[i];
sort(x + 1, x + m + 1);
int ans = 0, tot = m, cnt = 1;
for(int i = 2; i <= m; i ++) {
if(x[i] == x[i - 1] + 1) {
tot --;
cnt ++;
}
else {
tot ++;
ans += cnt - 2;
cnt = 1;
}
if(x[i] == x[i - 1] + 2) ans ++;
if(x[i] - x[i - 1] > 2) d[++ idx] = x[i] - x[i - 1] - 1;
}
if(x[1] + n - x[m] > 2) d[++ idx] = x[1] + n - x[m] - 1;
if(x[1] + n - x[m] == 1) {
cnt ++;
ans += cnt - 2;
}
else tot ++, ans += cnt - 2;
if(x[1] + n - x[m] == 2) ans ++;
sort(d + 1, d + idx + 1, [](int a, int b) {return ((a & 1) == (b & 1)) ? (a < b) : ((a & 1) > (b & 1));});
for(int i = 1; i <= idx; i ++) {
if(y >= d[i] / 2) ans += d[i], y -= d[i] / 2;
else {
ans += y * 2;
break;
}
}
cout << ans + tot - 2 << '\n';
return ;
}
D. Learning to Paint
考慮 DP,設 \(f_{i, j}\) 表示前 \(i\) 個位置第 \(j\) 大的答案是多少。
轉移來自所有 \(p < i\) 位置的前 \(k\) 大,轉移總數很多但是我們只要前 \(k\) 大,所以用堆維護即可。
int n, k, a[N][N], f[N][N], g[N];
priority_queue<PII> heap;
void work() {
cin >> n >> k;
for(int i = 1; i <= n; i ++) {
for(int j = i; j <= n; j ++)
cin >> a[i][j];
}
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= k; j ++) f[i][j] = -INF;
f[0][1] = 0;
for(int i = 1; i <= n; i ++) {
while(heap.size()) heap.pop();
for(int j = 1; j <= i - 1; j ++) {
g[j] = 1;
heap.push({f[j - 1][1] + a[j + 1][i], j});
}
heap.push({a[1][i], 0});
for(int j = 1; j <= k; j ++) heap.push({f[i - 1][j], -1});
for(int j = 1; j <= k; j ++) {
if(heap.empty()) break;
auto [x, y] = heap.top(); heap.pop();
f[i][j] = x;
if(y > 0 && g[y] < k) heap.push({f[y - 1][++ g[y]] + a[y + 1][i], y});
}
}
for(int i = 1; i <= k; i ++) cout << f[n][i] << ' '; cout << '\n';
return ;
}
E. Farm Game
A 的牛隻有向右走的時候才會給局面帶來改變,因為二者輪流動,顯然相鄰是必敗態,以此類推可以知道後者獲勝當且僅當所有樣式 AB 之間的距離都是偶數。
考慮計數這個東西,因為對稱,所以只需要計數 A 在左邊的情況。可以列舉所有 AB 的間隔和,然後用除以 2 用擋板法算出來一種間隔和的貢獻,因為還要放置 AB,這部分的方案數是:
\[\binom{l - i - n}{n}
\]
可以看作把 \(l\) 裡面去掉 \(i\) 段,這樣需要組合 \(n\) 相鄰點,減掉 \(n\) 段就是組合數了。
// LUOGU_RID: 155511987
// Problem: Farm Game
// Author: Moyou
// Copyright (c) 2024 Moyou All rights reserved.
// Date: 2024-04-12 19:11:35
#include <iostream>
using namespace std;
const int N = 2e6 + 10, mod = 998244353, M = N;
int n, l;
int qmi(int a, int b) {
int res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
b >>= 1; a = 1ll * a * a % mod;
}
return res;
}
int fac[M], ifac[M];
void pre() {
fac[0] = ifac[0] = 1;
for(int i = 1; i <= M - 10; i ++) fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[M - 10] = qmi(fac[M - 10], mod - 2);
for(int i = M - 11; i; i --) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
}
int C(int n, int m) {
if(n < m) return 0;
return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
void work() {
cin >> l >> n;
int ans = 0;
for(int i = 0; i <= l - n * 2; i += 2)
ans = (ans + 1ll * C(l - i - n, n) * C(i / 2 + n - 1, n - 1) % mod) % mod;
cout << (2ll * (C(l, n * 2) - ans) % mod + mod) % mod << '\n';
return ;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
pre();
int T = 1;
cin >> T;
while (T--) work();
return 0;
}
總結
C1, C2切慢了,後面 D 沒寫完。
應當集中注意力不要發呆。