Codeforces Round 1049 (Div. 2)
A. Shift Sort
题意:一个\(01\)串,每次可以选择\(i, j, k\)三个位置让它们左移或右移,求使得字符串升序的最小操作数。
记有\(cnt\)个\(0\),如果一个\(0\)不在前\(cnt\)个位置里,那么它需要一次交换,且每次交换最多让一个\(0\)复位。所以这样位置的个数就是答案。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::string s;
std::cin >> n >> s;
int cnt = std::ranges::count(s, '0');
int ans = 0;
for (int i = 0; i < cnt; ++ i) {
ans += s[i] == '1';
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. Another Divisibility Problem
题意:给你一个\(x\),求一个\(y\),使得\(x+y\)整除\(x\)和\(y\)拼接后的数字。
记\(y\)有\(k\)位,那么\(x+y = x\times 10^k + y\),减去一个\(x+y\)后等于\(x\times (10^k - 1)\),这个数要整除\(x+y\),那么\(x+y\)得是\(x\)的倍数,则\(y\)是\(x\)的倍数。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 x;
std::cin >> x;
for (i64 y = x; ; y += x) {
std::string s = std::to_string(x) + std::to_string(y);
i64 z = 0;
for (auto & x : s) {
z = z * 10 + x - '0';
}
if (z % (x + y) == 0) {
std::cout << y << "\n";
return;
}
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
C. Ultimate Value
题意:一个数字\(a\)的代价为加上奇数位的和减去偶数的的和加\(cost\),其中\(cost\)为操作中产生的代价。\(Alice\)和\(Bob\)两个人轮流操作,\(Alice\)先手,每次操作选择一个\(i, j(i < j)\)交换\(a_i, a_j\),\(cost\)增加\(j - i\),或者终止操作。\(Alice\)希望代价最大,\(Bob\)希望最小。求最终代价。
结论是\(Alice\)要么直接终止,要么操作一次后\(Bob\)直接终止。
因为如果轮到\(Bob\)操作,发现交换\(i, j\)会减少代价,那么之后\(Alice\)可以重复交换\(i, j\),这样数组没变,代价增加了\(2\times(j-i)\)。不如直接终止比赛。
那么问题就变为最多交换一次后的最大代价。
如果交换的数的位置奇偶性相同,代价为\(j-i\)。
如果\(i\)是奇数,\(j\)是偶数,那么代价增加\(2\times a_j + j - 2\times a_i - i\)。这个可以从后往前做,记录一个\(max2\)表示偶数位置最大的\(2\times a_j - j\)。
同理如果\(i\)是偶数,\(j\)是奇数,代价增加\(2\times a_i - i - 2\times a_j - j\)。也可以维护一个\(max1\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
i64 ans = 0;
for (int i = 0; i < n; ++ i) {
ans += i % 2 ? -a[i] : a[i];
}
i64 max = n % 2 ? n - 1 : n - 2;
i64 max1 = -1e18, max2 = -1e18;
for (int i = n - 1; i >= 0; -- i) {
if (i % 2 == 0) {
max = std::max(max, max2 - 2 * a[i] - (i + 1));
max1 = std::max(max1, -2 * a[i] + (i + 1));
} else {
max = std::max(max, max1 + 2 * a[i] - (i + 1));
max2 = std::max(max2, 2 * a[i] + (i + 1));
}
}
std::cout << ans + max << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. A Cruel Segment's Thesis
题意:\(n\)个\([l, r]\),价值原来的所有\(r-l\)的和,然后你需要两两匹配,价值加上\(l_i \leq x \leq r_i, l_j \leq y \leq r_j\)的\(y - x\)的最大值。如果\(n\)是偶数则留一个不匹配。求最大价值。
一个区间对答案的贡献是要么被加上\(r\),要么被减去\(l\)。那么加上所有区间的\(r\)都是加上的,然后考虑选出\(\lfloor \frac{n}{2} \rfloor\)个\(l_i\)减去,这样的区间因为之前加过\(r\),现在又减去\(l\),所以贡献加上\(-r_i - l_i\)。那么选出最大的\(\lfloor \frac{n}{2} \rfloor\)个这样的贡献。
然后如果\(n\)是偶数,可以匹配完,直接加上前一半就行,如果\(n\)是奇数,考虑枚举不匹配的区间,然后根据这个区间的\(-r_i - l_i\)是不是前\(\lfloor \frac{n}{2} \rfloor\)个大的讨论即可,选择使得贡献减少最小的区间。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
i64 ans = 0;
std::vector<i64> l(n), r(n);
std::vector<std::pair<i64, int>> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> l[i] >> r[i];
ans += r[i] - l[i];
a[i] = {-r[i] - l[i], i};
ans += r[i];
}
std::ranges::sort(a, std::greater<>());
std::vector<int> st(n);
i64 sum = 0;
for (int i = 0; i < n / 2; ++ i) {
sum += a[i].first;
st[a[i].second] = 1;
}
if (n % 2 == 0) {
ans += sum;
} else {
i64 max = -1e18;
for (int i = 0; i < n; ++ i) {
if (!st[i]) {
max = std::max(max, -r[i] + sum);
} else {
max = std::max(max, sum - (-r[i] - l[i]) - r[i] + a[n / 2].first);
}
}
ans += max;
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E1. Prime Gaming (Easy Version)
题意:有\(n\)堆石头,每堆的石头数在\([1, m]\)之间。有些位置是可以操作的,\(Alice\)和\(Bob\)轮流操作,每次选择一个可以操作的位置把这个位置上的石堆删掉,然后后面的往前移。最后剩下的一堆的石头数就是价值。\(Alice\)希望价值最大,\(Bob\)希望价值最小。求所有情况下的价值总和。
\(n \leq 20, m \leq 2\)。
如果\(m=1\),答案是\(1\)。
记\(f[i][0/1][s]\)表示长度为\(i\)的数组,轮到\(Alice/Bob\)操作,且状态为\(s\)能否取到\(2\)。其中\(s\)是一个二进制数,第\(i\)位为\(0\)表示第\(i\)堆石头有一个石头,否则有两个石头。
那么枚举操作的位置,预处理一个\(ns[s][k]\)表示删掉\(s\)的第\(k\)位后的状态,就可以枚举操作的位置转移,其中\(Alice\)只需要有一个为\(1\)的状态能转移过来就是\(1\),\(Bob\)只需要有一个为\(0\)的状态转移过来就是\(0\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int mod = 1e9 + 7;
bool f[21][2][1 << 20];
int ns[1 << 20][20];
int c[20];
void init() {
for (int s = 0; s < 1 << 20; ++ s) {
int x = 0;
for (int i = 0; i < 20; ++ i) {
int y = x;
for (int j = i + 1; j < 20; ++ j) {
if (s >> j & 1) {
y |= 1 << (j - 1);
}
}
ns[s][i] = y;
if (s >> i & 1) {
x |= 1 << i;
}
}
}
}
void solve() {
int n, m;
std::cin >> n >> m;
int k;
std::cin >> k;
memset(c, 0, sizeof c);
for (int i = 0; i < k; ++ i) {
int x;
std::cin >> x;
c[x - 1] = 1;
}
if (m == 1) {
std::cout << 1 << "\n";
return;
}
f[1][0][1] = f[1][1][1] = 1;
f[1][0][0] = f[1][1][0] = 0;
for (int i = 2; i <= n; ++ i) {
for (int s = 0; s < 1 << i; ++ s) {
f[i][0][s] = 0;
for (int k = 0; k < i; ++ k) {
if (c[k]) {
f[i][0][s] |= f[i - 1][1][ns[s][k]];
}
}
f[i][1][s] = 1;
for (int k = 0; k < i; ++ k) {
if (c[k]) {
f[i][1][s] &= f[i - 1][0][ns[s][k]];
}
}
}
}
int ans = 0;
for (int s = 0; s < 1 << n; ++ s) {
ans = (ans + f[n][0][s] + 1) % mod;
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
init();
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E2. Prime Gaming (Hard Version)
题意:与\(e1\)的区别是,\(m\)可以达到\(1e6\)。
如果枚举最终留下的值\(k\),则数组每个数如果大于等于\(k\)可以表示为\(1\),否则表示为\(0\)。这样就转换成了\(e1\)。如果\(f[n][0][s]\)是\(1\),则记\(a\)为\(s\)中\(1\)的个数,\(b\)为\(0\)的个数,则有\((m-k+1)^a(k-1)^b\)种情况的贡献大于等于\(k\)。
因为是大于等于\(k\),所以对于一个\(k\),其中在枚举\([1, k]\)是都可能使得留下的最后一个数是\(k\),那么我们每种情况的贡献就直接算是\((m-k+1)^a(k-1)^b\),这样每个数在枚举\([1, k]\)的每种情况中都会算上\(1\)的贡献,这样相当于把一个数拆成了若干个\(1\),所有总的情况加起来,\(k\)还是对每种情况造成了\(k\)的贡献。
不过我们不能直接枚举\(s\)然后枚举\(m\),这样会超时,发现\((m-k+1)^a(k-1)^b\)这个式子中,\(m\)是常量,只有\(k, a, b\)是变量,其中\(b = n - a\),那么我们只需要枚举\(k\)然后枚举\(a\)就行。那么先枚举所有\(s\),把\(f[n][0][s]\)为\(1\)的状态的\(a\)记录\(cnt[a]\)中,则对于一个\(k\)和一个\(a\)贡献为\(cnt[a] \times(m-k+1)^a(k-1)^b\)。
代码省略取模类。
点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;
bool f[21][2][1 << 20];
int ns[1 << 20][20];
int c[20];
void init() {
for (int s = 0; s < 1 << 20; ++ s) {
int x = 0;
for (int i = 0; i < 20; ++ i) {
int y = x;
for (int j = i + 1; j < 20; ++ j) {
if (s >> j & 1) {
y |= 1 << (j - 1);
}
}
ns[s][i] = y;
if (s >> i & 1) {
x |= 1 << i;
}
}
}
}
void solve() {
int n, m;
std::cin >> n >> m;
int k;
std::cin >> k;
memset(c, 0, sizeof c);
for (int i = 0; i < k; ++ i) {
int x;
std::cin >> x;
c[x - 1] = 1;
}
if (m == 1) {
std::cout << 1 << "\n";
return;
}
f[1][0][1] = f[1][1][1] = 1;
f[1][0][0] = f[1][1][0] = 0;
for (int i = 2; i <= n; ++ i) {
for (int s = 0; s < 1 << i; ++ s) {
f[i][0][s] = 0;
for (int k = 0; k < i; ++ k) {
if (c[k]) {
f[i][0][s] |= f[i - 1][1][ns[s][k]];
}
}
f[i][1][s] = 1;
for (int k = 0; k < i; ++ k) {
if (c[k]) {
f[i][1][s] &= f[i - 1][0][ns[s][k]];
}
}
}
}
Z ans = 0;
std::vector<int> cnt(n + 1);
for (int s = 0; s < 1 << n; ++ s) {
if (!f[n][0][s]) {
continue;
}
int a = __builtin_popcount(s);
cnt[a] += 1;
}
for (int i = 1; i <= n; ++ i) {
for (int k = 1; k <= m; ++ k) {
ans += cnt[i] * power<Z>(m - k + 1, i) * power<Z>(k - 1, n - i);
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
init();
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}