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 10k + y$,减去一个$x+y$后等于$x\times (10k - 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{2} \rfloor$个$l_i$减去,这样的区间因为之前加过$r$,现在又减去$l$,所以贡献加上$-r_i - l_i$。那么选出最大的$\lfloor \frac{2} \rfloor$个这样的贡献。
然后如果$n$是偶数,可以匹配完,直接加上前一半就行,如果$n$是奇数,考虑枚举不匹配的区间,然后根据这个区间的$-r_i - l_i$是不是前$\lfloor \frac{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;
}

浙公网安备 33010602011771号