Educational Codeforces Round 170 (Rated for Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/2025。
妈的不想上学省赛回来昏了一天了。
A 签到
发现最优的操作是先在一个屏幕操作得到最长公共前缀,然后复制到另一方上,再分别操作两方。
特判若无公共前缀时,独立操作两方更优。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::string s, t;
std::cin >> s >> t;
int n = s.length(), m = t.length(), nm = 0;
for (int i = 0; i < std::min(n, m); ++ i) {
if (s[i] == t[i]) ++ nm;
else break;
}
if (nm) std::cout << (n + m - nm + 1) << "\n";
else std::cout << n + m << "\n";
}
return 0;
}
B 结论
手推几层这个倒塌的杨辉三角很容易发现:
然后做完了。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const LL p = 1e9 + 7;
//=============================================================
LL n[kN], k[kN], pw2[kN];
//=============================================================
LL solve (LL n_, LL k_) {
if (k_ == 0 || k_ == n_) return 1;
return pw2[k_];
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
pw2[0] = 1;
for (int i = 1; i < kN; ++ i) pw2[i] = pw2[i - 1] * 2ll % p;
int T; std::cin >> T;
for (int i = 1; i <= T; ++ i) std::cin >> n[i];
for (int i = 1; i <= T; ++ i) std::cin >> k[i];
for (int i = 1; i <= T; ++ i) {
std::cout << solve(n[i], k[i]) << "\n";
}
return 0;
}
C 双指针
考虑记录每种权值出现次数,并将所有权值排序,问题即求所有连续的极差不超过 \(k\) 的权值区间出现次数之和的最大值。
套路地双指针即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, k, a[kN], cnt[kN];
std::map<int, int> b;
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> k;
b.clear();
for (int i = 1; i <= n; ++ i) std::cin >> a[i], ++ b[a[i]];
n = 0;
for (auto [x, y]: b) a[++ n] = x, cnt[n] = y;
LL ans = 0, sum = 0;
for (int l = 1, r = 0; l <= n; ) {
if (l > r) r = l - 1, sum = 0;
while (r + 1 <= n && (r - l + 1) < k) {
if (sum == 0) sum += cnt[r + 1], ++ r;
else if (a[r + 1] == a[r] + 1) sum += cnt[r + 1], ++ r;
else break;
}
ans = std::max(ans, sum);
sum -= cnt[l], ++ l;
}
std::cout << ans << "\n";
}
return 0;
}
D 模拟,差分,DP,结论
首先有限制 \(m\le 5000\),那么若已有了 \(2\times m\) 个 0,之后的 check 一定都有贡献。
然后考虑一个显然的暴力,设 \(f_{i, j}\) 表示进行到某一回合,当前智力为 \(i\),力量为 \(j\) 时可以得到的最大价值,转移时 \(r=0\) 即新增一条合法的对角线,\(r \not= 0\) 则枚举所有合法状态并全部加 1,可以发现这些合法状态构成一个以 \((m, m)\) 为右下角的矩形。
然后发现在暴力里,每回合的合法状态实际上只有一条对角线上的 \(O(m)\) 个,所以只需要维护对角线的状态即可。记 \(f_{i}\) 表示在某一回合智力为 \(i\) 时可以得到的最大价值,\(r=0\) 直接根据当前 \(f\) 构造新的 \(f\),\(r\not= 0\) 时发现受影响的状态在对角线上是连续的前缀或后缀,于是考虑用差分维护 \(f\) 进行区间加即可。
上述 \(r=0\) 的转移复杂度 \(O(m)\) 级别,\(r\not= 0\) 转移复杂度 \(O(1)\),至多进行 \(2\times m\) 次 \(r=0\) 的转移,则转移总数是 \(O(m^2 + n)\) 级别的。
最后还原数组并求最大值即可。总时间复杂度 \(O(n + m^2)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kM = 5010;
//=============================================================
int n, m, cnt, f[kM], temp[kM];
//=============================================================
void modify(int l_, int r_) {
++ f[l_], -- f[r_ + 1];
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
int a; std::cin >> a;
if (cnt >= 2 * m) {
if (a != 0) ++ f[m];
continue;
}
if (a == 0) {
for (int i = 1; i <= std::min(m, cnt); ++ i) f[i] += f[i - 1];
++ cnt;
for (int i = 0; i <= std::min(m, cnt); ++ i) temp[i] = 0;
for (int i = std::max(0, cnt - m); i <= std::min(m, cnt); ++ i) {
temp[i] = f[i];
if (i > 0) temp[i] = std::max(temp[i], f[i - 1]);
}
for (int i = 0; i <= std::min(m, cnt); ++ i) f[i] = 0;
for (int i = std::max(0, cnt - m); i <= std::min(m, cnt); ++ i) {
if (i == 0) f[i] = temp[i];
if (i > 0) f[i] = temp[i] - temp[i - 1];
}
} else if (a > 0) {
if (a > cnt) continue;
modify(std::max(a, cnt - m), std::min(m, cnt));
} else {
a = -a;
if (a > cnt) continue;
modify(cnt - std::min(m, cnt), cnt - std::max(a, cnt - m));
}
}
int ans = 0;
for (int i = 1; i <= m; ++ i) f[i] += f[i - 1];
if (cnt >= 2 * m) {
ans = f[m];
} else {
for (int i = std::max(0, cnt - m); i <= std::min(m, cnt); ++ i) {
ans = std::max(ans, f[i]);
}
}
std::cout << ans << "\n";
return 0;
}
E 计数,DP,组合数学
先仅考虑 \(n=1\) 的牌,问题等价于为两方各选 \(\frac{m}{2}\) 个数,保证玩家 2 的每一张牌,都有一张比它大的牌在玩家 1 的手中与之匹配,发现可以将玩家 1 选的牌看做右括号,玩家 2 选的看做左括号进行匹配,则方案数等价于长度为 \(n\) 的括号序列的种类数。
然后再考虑 \(n>1\) 的牌,因为 \(n=1\) 的牌可以打败所有其他的牌,因此玩家 1 可以通过多拿一些 \(n=1\) 的牌,来与 \(n>1\) 的牌匹配,即若也将选牌情况看做括号序列,等价于允许某些(至多 \(m\) 个)左括号未匹配的括号序列的种类数。
记 \(F_k\) 表示长度为 \(m\) 的括号序列,有 \(k(2 | k)\) 个右括号未匹配的方案数。这是个经典问题,显然该序列中一定有 \(\frac{m-k}{2}\) 个左括号,\(m-\frac{m-k}{2} = \frac{m+k}{2}\) 个右括号;进一步地,可以转化为至少有 \(\frac{m+k}{2}\) 个右左括号减去至少有 \(\frac{m+k}{2}+1\) 个右括号的方案数,即有:
于是考虑枚举每种牌里与 \(n=1\) 的匹配数量 \(j(0\le j\le m)\),记 \(f_{i, j}\) 表示考虑到前 \(i\) 种牌,当前有 \(j\) 张 \(n=1\) 还未匹配(即还可以匹配多少左括号)的方案数,转移时枚举匹配完当前种类牌后还剩 \(k\) 张 \(n=1\) 的牌,则有:
答案即 \(F_{n, 0}\),总时间复杂度 \(O(nm^2)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1010;
const LL p = 998244353;
//=============================================================
int n, m;
LL f[kN][kN], fac[kN], invfac[kN];
//=============================================================
LL calc(LL k_) {
LL ret = fac[m] * invfac[(m - k_) / 2] % p * invfac[(m + k_) / 2] % p;
ret = (ret - fac[m] * invfac[(m - k_) / 2 - 1] % p * invfac[(m + k_) / 2 + 1] % p + p) % p;
return ret;
}
LL qpow(LL x_, LL y_) {
LL ret = 1;
while (y_) {
if (y_ & 1) ret = ret * x_ % p;
x_ = x_ * x_ % p, y_ >>= 1ll;
}
return ret;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
fac[0] = 1;
for (int i = 1; i < kN; ++ i) fac[i] = fac[i - 1] * i % p;
invfac[kN - 1] = qpow(fac[kN - 1], p - 2);
for (int i = kN - 2; ~i; -- i) invfac[i] = invfac[i + 1] * (i + 1) % p;
for (int i = 0; i <= m; i += 2) f[1][i] = calc(i);
for (int i = 2; i <= n; ++ i) {
for (int j = 0; j <= m; ++ j) {
for (int k = 0; k <= j; ++ k) {
if ((j + m - k) % 2) continue;
f[i][k] = (f[i][k] + f[i - 1][j] * calc(j - k) % p) % p;
}
}
}
std::cout << f[n][0] << "\n";
return 0;
}
F 转化,树,结论
手玩下容易发现最终每个位置一定为 0/1,因此最优方案中,每次操作等价于选择位置并异或 1,即仅需考虑奇偶性即可。
一个显然的想法是先令所有操作均对 \(x_i\) 进行,则可得到一种令每个位置均为 0/1 的操作方案,然后考虑考虑将某些操作调整到对 \(y_i\) 进行以最小化所有位置之和。发现若调整某个操作至 \(y_i\),仅会影响两个操作数,但是需要链式地考虑一系列位置的取值,来决定是否需要调整。考虑将操作看做无向边建图,可得到若干联通块,问题转化为对于一张无向图上,点有点权 0/1,可以选择任意条边,选择一条边可令两个端点均异或 1。
发现若某些操作构成了一条链,很容易构造出选边方案,使得在这条链上所有点权初值任意情况下,使得这条链的权值之和不超过 1(即至多有一个点权值为 1);同样地,很容易将上述构造扩展到树上,仅需从叶节点开始向上 DP,每次根据当前点奇偶性,考虑是否选择当前点连向父节点的边即可;进一步地发现,成环的情况与链的情况是一致的,若构造链使得有一个点权值为 1,其他点权值已均为 0,再多选一条边也无法使所有点权变为 0;进一步地可以扩展到任意无向连通图,则仅需考虑该图的一棵任意生成树,按照上述方法进行构造即可。
构造后即可得到哪些边需要调整,即可得到所有边对应的操作的位置;然后顺序枚举操作,根据当前被操作位置的点权确定 +/- 即可。
具体实现详见代码,总时间复杂度 \(O(n+q)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 3e5 + 10;
//=============================================================
int n, q, x[kN], y[kN], val[kN];
int sz[kN];
bool vis[kN], modified[kN];
std::vector<pii> edge[kN];
//=============================================================
void dfs(int u_, int id_) {
vis[u_] = 1;
for (auto [v_, id_]: edge[u_]) {
if (vis[v_]) continue;
dfs(v_, id_);
sz[u_] += sz[v_];
}
if (sz[u_] % 2 && id_ != 0) modified[id_] = 1;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> q;
for (int i = 1; i <= q; ++ i) {
std::cin >> x[i] >> y[i];
edge[x[i]].push_back(mp(y[i], i));
edge[y[i]].push_back(mp(x[i], i));
sz[x[i]] ^= 1;
}
for (int i = 1; i <= n; ++ i) if (!vis[i]) dfs(i, 0);
for (int i = 1; i <= q; ++ i) {
int p = x[i]; char ch = 'x';
if (modified[i]) p = y[i], ch = 'y';
if (val[p]) std::cout << ch << "-\n", -- val[p];
else std::cout << ch << "+\n", ++ val[p];
}
return 0;
}
写在最后
感觉这辈子没这么厌学过。
高中好歹还有“上了大学我要干…”这种美好的幻想现在上学又学不到东西又浪费时间而且为了有学上还不得不去卷卷也学不到东西只会更痛苦受不了了呃呃呃呃