Codeforces Round 1020 (Div. 3)
A. Dr. TC
题意:通过把\(01\)串每一位依次取反得到\(n\)个\(01\)串,求这些串里\(1\)的个数。
记\(cnt_0\)为串里\(0\)的个数,\(cnt_1\)为\(1\)的个数,那么翻转一个\(0\)使得\(1\)的个数加一,翻转一个\(1\)使得\(1\)的个数减一,那么答案就是\(cnt_0 \times (cnt_1 + 1) + cnt_1 \times (cnt_1 - 1)\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
i64 cnt1 = std::ranges::count(s, '1'), cnt0 = std::ranges::count(s, '0');
i64 ans = cnt1 * (cnt1 - 1) + cnt0 * (cnt1 + 1);
std::cout << ans << "\n";
}
B. St. Chroma
题意:给你\(n, x\),构造一个\([0, n - 1]\)的排列,使得\(x\)在前缀\(mex\)里出现的最多。
首先应该把\([0, x - 1]\)放到前面,这样后面的\(mex\)就大于等于\(x\),然后把\(x\)放到最后,中间随便放,则中间这些前缀的\(mex\)都是\(x\)。
点击查看代码
void solve() {
int n, x;
std::cin >> n >> x;
std::vector<int> ans(n);
for (int i = 0; i < x; ++ i) {
ans[i] = i;
}
for (int i = x, j = n - 1; i < n; ++ i) {
ans[i] = j -- ;
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] << " \n"[i == n - 1];
}
}
C. Cherry Bomb
题意:两个数组\(a, b\),值域为\([0, k]\)。有\(i, j\in [1, n], a_i + b_i = a_j + b_j\)。现在\(b\)有些位置没填,求合法的\(b\)的个数。
如果\(b\)不全是没填的位置,那么可以确定\(a_i + b_i\)的值,判断是不是每个对的和相等,如果相等判断\(b_i\)有没有超出范围。都合法则只有一种,否则无解。
讨论\(b\)全是\(-1\)的情况。去\(max, min\)为\(a\)中最大最小值,那么对于最小值对应的\(b_i\),至少是\(max - min\),最多是\(k\),那么答案就是\(k - (max - min) + 1\)。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n), b(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i < n; ++ i) {
std::cin >> b[i];
}
int x = -1;
for (int i = 0; i < n; ++ i) {
if (b[i] != -1) {
if (x != -1 && a[i] + b[i] != x) {
std::cout << 0 << "\n";
return;
} else {
x = a[i] + b[i];
}
}
}
if (x != -1) {
for (int i = 0; i < n; ++ i) {
if (b[i] == -1 && (x - a[i] < 0 || x - a[i] > k)) {
std::cout << 0 << "\n";
return;
}
}
std::cout << 1 << "\n";
} else {
int min = *std::min_element(a.begin(), a.end());
int max = *std::max_element(a.begin(), a.end());
std::cout << k - (max - min) + 1 << "\n";
}
}
D. Flower Boy
题意:两个数组\(a, b\),从左到右从\(a\)中数,满足第\(j\)个选出的数有\(a_i \geq b_j\)。为了满足操作,你可以在中间插入一个数,求这个数的最小值。
记\(pre_i\)为取完\([1, i]\)的\(b\)的前缀\(a\)中到的位置,\(suf_i\)表示取完\([i, m]\)的后缀到的位置。
那么如果要插入这个数,那么这个数一定是用来顶替一个\(b_i\)的,我们枚举这个\(b_i\),如果\(pre_{i-1} < suf_{i+1}\)就可以使得我们顶替它后可以把其它数都取完,所有这样的\(b_i\)选一个最小的。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1), b(m + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
for (int i = 1; i <= m; ++ i) {
std::cin >> b[i];
}
std::vector<int> pre(m + 1, n + 1);
pre[0] = 0;
for (int i = 1, j = 1; i <= n && j <= m; ++ i) {
if (a[i] >= b[j]) {
pre[j] = i;
++ j;
}
}
std::vector<int> suf(m + 2, -1);
suf[m + 1] = n + 1;
for (int i = n, j = m; i > 0 && j > 0; -- i) {
if (a[i] >= b[j]) {
suf[j] = i;
-- j;
}
}
if (pre[m] <= n) {
std::cout << 0 << "\n";
return;
}
int ans = 2e9;
for (int i = 1; i <= m; ++ i) {
if (suf[i + 1] > pre[i - 1]) {
ans = std::min(ans, b[i]);
}
// std::cout << pre[i] << " \n"[i == m];
}
if (ans == 2e9) {
ans = -1;
}
std::cout << ans << "\n";
}
E. Wolf
题意:一个二分查找程序在一个排列的\([l, r]\)子区间上查找\(x\)。这个排列不一定是有序的,所以可能无法正确查询到\(x\)。对于每次查询,你可以选\(k\)个不包含\(x\)的数,将他们的位置重新排列,使得可以查询到\(x\)。求最小的\(k\)。
记\(p_i\)为\(i\)在排列中的位置。
首先如果\(i\)不在\([l, r]\)内,因为不能动\(x\),所以不可能查询到。
如果在,我们模拟一遍,把所有中点取出来,如果当前中点可以让程序走正确的一边,则不用官,否则我们需要找一个数和它交换。具体可以求出,有\(cnt_1\)个数需要和比\(x\)小的数交换,有\(cnt_2\)个数需要和比\(x\)大的数交换。那么分类讨论一下就可以得到需要更改的最少的数。
点击查看代码
void solve() {
int n, q;
std::cin >> n >> q;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
-- a[i];
}
std::vector<int> p(n);
for (int i = 0; i < n; ++ i) {
p[a[i]] = i;
}
auto get = [&](int l, int r, int x) -> int {
int used1 = x, used2 = n - 1 - x;
int cnt1 = 0, cnt2 = 0;
auto f = [&](auto & self, int l, int r) -> void {
if (l >= r) {
return;
}
int mid = l + r >> 1;
if (a[mid] == x) {
return;
}
if (p[x] < mid) {
if (a[mid] < x) {
++ cnt2;
-- used1;
} else {
-- used2;
}
self(self, l, mid - 1);
} else {
if (a[mid] > x) {
++ cnt1;
-- used2;
} else {
-- used1;
}
self(self, mid + 1, r);
}
};
f(f, l, r);
int res = std::min(cnt1, cnt2);
// std::cout << cnt1 << " " << cnt2 << "\n";
cnt1 -= res, cnt2 -= res;
if (cnt1 > 0) {
if (used1 < cnt1) {
return -1;
}
res += cnt1;
} else if (cnt2 > 0) {
if (used2 < cnt2) {
return -1;
}
res += cnt2;
}
return res * 2;
};
while (q -- ) {
int l, r, x;
std::cin >> l >> r >> x;
-- l, -- r, -- x;
if (l > p[x] || r < p[x]) {
std::cout << -1 << " \n"[!q];
continue;
}
std::cout << get(l, r, x) << " \n"[!q];
}
}
F. Goblin
题意:一个\(01\)串\(a\),把\(a\)复制\(n\)份组成\(01\)矩阵\(g\),然后把所有\(g[i][i]\)取反。求\(g\)中\(0\)联通块的最大大小。
发现可以把数组分成两部分,一种是主对角线左边的三角部分,这个部分的\(g[i][j] = s[j]\),一种是右边的三角部分,同样有\(g[i][j] = s[j]\)。那么我们可以把这些三角的一列当作一个元素,用并查集维护这些元素,每列初始有多少\(0\)也可以求出来。然后把这两部分分别合并一下,然后想要合并这两个三角形,只能通过对角线上的\(0\),那么枚举对角线上的\(0\), 分别和左边的列已经右边的列合并。注意合并的前提是两个元素都是\(0\)。
那么就得到了每个联通块的\(0\)的个数。记得开longlong。
点击查看代码
struct DSU {
std::vector<i64> fa, cnt;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n, 0);
cnt.assign(_n, 1ll);
std::iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
cnt[x] += cnt[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
i64 size(int x) {
return cnt[find(x)];
}
};
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
if (n == 1) {
std::cout << (s[0] - '0') << "\n";
return;
}
auto get = [&](int t, int x) -> int {
if (t == 0) {
return x;
} else if (t == 1) {
return n - 2 + x;
} else {
return 2 * n - 3 + x + 1;
}
};
DSU d(2 * (n - 1) + n);
for (int i = 0; i + 1 < n; ++ i) {
if (s[i] == '0') {
d.cnt[get(0, i)] = n - 1 - i;
}
}
for (int i = 1; i < n; ++ i) {
if (s[i] == '0') {
d.cnt[get(1, i)] = i;
}
}
for (int i = 0; i < n; ++ i) {
if (s[i] == '1') {
if (i && s[i - 1] == '0') {
d.merge(get(0, i - 1), get(2, i));
}
if (i + 1 < n && s[i + 1] == '0') {
d.merge(get(1, i + 1), get(2, i));
}
}
}
for (int i = 0; i + 1 < n - 1; ++ i) {
if (s[i] == '0' && s[i + 1] == '0') {
d.merge(get(0, i), get(0, i + 1));
}
}
for (int i = 1; i + 1 < n; ++ i) {
if (s[i] == '0' && s[i + 1] == '0') {
d.merge(get(1, i), get(1, i + 1));
}
}
i64 ans = 0;
for (int i = 0; i < 2 * (n - 1) + n; ++ i) {
if (d.find(i) == i) {
ans = std::max(ans, d.size(i));
}
}
std::cout << ans << "\n";
}