目录
写在前面
比赛地址:https://codeforces.com/contest/2030。
赛时 E 看错题了变成神题了而且居然还口胡了一个自然根号的做法还写出来了然而样例没过最后才发现读错题了妈的。
掉分!
A 签到
\(b, c\) 即前缀最小值和最大值,显然最优的构造是把最大值和最小值放在前两个位置,答案即:
\[(n-1)\left(\max a_i - \min a_i\right) \]//
/*
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 --) {
int n; std::cin >> n;
int mina = 1100, maxa = -1;
for (int i = 1; i <= n; ++ i) {
int a; std::cin >> a;
mina = std::min(mina, a);
maxa = std::max(maxa, a);
}
std::cout << (1ll * (n - 1) * (maxa - mina)) << "\n";
}
return 0;
}
B 构造
因为是 01 串且选的是子序列,所以并不需要关注选的位置,仅需关注选的字符种类即可。
设构造的串 \(t\) 里有 \(k\) 个 0,则 \(f(t) = 2^k - 1, g(t) = 2^n - 1 - f(t)\),则 \(|f(t) - g(t)| = |2^{k +1} - 2^n|\),
仅需取 \(k=n-1\) 即可令 \(|f(t)-g(t)| = 0\),于是随便构造一个有 \(n-1\) 个 0,1 个 1 的串即可。
//
/*
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 --) {
int n; std::cin >> n;
for (int i = 1; i <= n - 1; ++ i) std::cout << 0;
std::cout << 1;
std::cout << "\n";
}
return 0;
}
C 博弈
显然若第一个或最后一个字符为 1,则 Alice 必胜。
注意到题面中特别指出 and 运算优先级更高,则发现若有两个 1 相邻,则 Alice 一定可以在将两者之一的两侧均插入一个 or 从而必胜,否则若所有 1 均不相邻,则 Bob 可以通过优先级更高的 and 将所有 1 均变为 0;
于是仅需判断第一个字符、最后一个字符是否为 1,或是否有 1 相邻即可。
//
/*
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 --) {
int n; std::cin >> n;
std::string s; std::cin >> s;
int flag = 0;
for (int i = 0; i < n; ++ i) {
if (i == 0 && s[i] == '1') flag = 1;
if (i == n - 1 && s[i] == '1') flag = 1;
if (0 < i && s[i] == '1' && s[i - 1] == '1') flag = 1;
}
std::cout << (flag ? "YES\n" : "NO\n");
}
return 0;
}
D 模拟
发现将操作分为 \(L, R\) 是无必要的,先把所有操作均转化为 \((i-1, i)\) 的形式。
发现如果在某个位置 \(i\) 前面有大于 \(i\) 的数,说明操作 \((i-1, i)\) 是必要的。因为每次询问排列均为初始状态,则于是必要操作的种类是不变的,仅需考虑维护当前已经有了多少必要操作即可。
可以通过桶简单实现。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, q, p[kN], premax[kN], sufmin[kN];
int need[kN], have[kN];
std::string s;
//=============================================================
//=============================================================
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 >> q;
for (int i = 1; i <= n; ++ i) std::cin >> p[i], need[i] = have[i] = 0;
std::cin >> s; s = "$" + s;
for (int i = 1; i <= n; ++ i) premax[i] = std::max(premax[i - 1], p[i]);
sufmin[n + 1] = n + 1;
for (int i = n; i; -- i) sufmin[i] = std::min(sufmin[i + 1], p[i]);
int cnt = 0, needcnt = 0;
for (int i = 1; i <= n; ++ i) {
if (s[i] == 'L') ++ have[i - 1];
if (s[i] == 'R') ++ have[i];
}
for (int i = 1; i < n; ++ i) {
if (premax[i] > i || sufmin[i + 1] <= i) need[i] = 1;
needcnt += need[i];
cnt += (need[i] && have[i]);
}
while (q --) {
int x; std::cin >> x;
cnt -= (need[x - 1] && have[x - 1]);
cnt -= (need[x] && have[x]);
if (s[x] == 'L') -- have[x - 1], ++ have[x], s[x] = 'R';
else -- have[x], ++ have[x - 1], s[x] = 'L';
cnt += (need[x - 1] && have[x - 1]);
cnt += (need[x] && have[x]);
std::cout << (cnt == needcnt ? "YES" : "NO") << "\n";
}
}
return 0;
}
E 组合数学
结论缝合题。首先要推出来两个结论:
给定 \(n\) 个整数,记其中权值 \(i\) 的数量为 \(\operatorname{cnt}_i\),则 \(\operatorname{mex}\le i\) 的子集数量为:\(s_i = 2^{n} - 2^{\operatorname{cnt}_i}\),简单容斥可知 \(\operatorname{mex}= i\) 的子集数量为 \(s_i - s_{i - 1}\)。
给定 \(n\) 个整数,记其中权值 \(i\) 的数量为 \(\operatorname{cnt}_i\),将它们分为若干个集合 \(S_1\sim S_k\),最大化 \(\sum_j \operatorname{mex}(S_j)\),显然贪心地分配,每次将当前所有权值都拿出一个来创建一个新集合即可,最大价值为:\(\operatorname{cnt}_0 + \min(\operatorname{cnt}_0, \operatorname{cnt}_1) + \min(\operatorname{cnt}_0, \operatorname{cnt}_1, \operatorname{cnt}_2) + \cdots\)。
因为是选子序列,于是仅需考虑选择的各种权值的数量即可。根据上述结论,考虑升序枚举所有权值 \(i\),考虑该种权值产生了 \(j\) 的贡献(即分出来的子集中恰有 \(j\) 个 \(\operatorname{mex}> i\))的子序列数量有多少。考虑套用结论 1,先求 \(s_j\) 表示该种权值产生了 \(\ge j\) 的贡献的方案数 \(s_j\),则对于权值 \(0\sim i\),都需要选至少 \(j\) 个,对于其他权值任选,可以简单通过组合意义+前缀和得到,则总贡献即为:
\[\sum_{j=1}^{\min(\operatorname{cnt}_0, \cdots, \operatorname{cnt}_i)} j\times (s_{j + 1} - s_{j}) \]实现详见代码,总时间复杂度 \(O(n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const LL p = 998244353;
//=============================================================
int n, a[kN];
LL pw2[kN], fac[kN], invfac[kN], cnt[kN], sum[kN], pre[kN];
//=============================================================
LL C(LL x_, LL y_) {
if (y_ == 0) return 1;
if (y_ > x_) return 0;
return fac[x_] * invfac[y_] % p * invfac[x_ - y_] % p;
}
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);
pw2[0] = fac[0] = invfac[0] = 1;
for (int i = 1; i < kN; ++ i) {
pw2[i] = 2ll * pw2[i - 1] % p;
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;
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 0; i <= n; ++ i) cnt[i] = 0;
for (int i = 1; i <= n; ++ i) std::cin >> a[i], ++ cnt[a[i]];
LL ans = 0, mincnt = kN, sumcnt = 0;
for (int i = 1; i <= n; ++ i) pre[i] = 1;
for (int i = 0; i < n; ++ i) {
if (cnt[i] == 0) break;
for (int j = 0; j <= cnt[i] + 1; ++ j) sum[j] = 0;
LL sumc = 0;
mincnt = std::min(mincnt, cnt[i]), sumcnt += cnt[i];
for (int j = cnt[i]; j; -- j) {
sumc = (sumc + C(cnt[i], j)) % p;
pre[j] = pre[j] * sumc % p;
}
for (int j = 1; j <= mincnt; ++ j) {
sum[j] = 1ll * pre[j] * pw2[n - sumcnt] % p;
}
for (int j = 1; j <= mincnt; ++ j) {
ans += 1ll * j * (sum[j] - sum[j + 1] + p) % p;
ans %= p;
}
}
std::cout << ans << "\n";
}
return 0;
}
写在最后
学到了什么:
- E:考虑单个元素对集合的贡献之和。