[CF1942] CodeTON Round 8 (Div. 1 + Div. 2, Rated, Prizes! A~E 题解
[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 没写完。
应当集中注意力不要发呆。