比赛链接
官方题解
Problem A. Colorful Slimes 2
令输入中每一段连续的数字长度为 ,答案显然是 。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, a[MAXN];
int main() {
read(n);
int now = 0, cnt = 0, ans = 0;
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] == now) cnt++;
else {
now = a[i];
ans += cnt / 2;
cnt = 1;
}
}
ans += cnt / 2;
writeln(ans);
return 0;
}
Problem B. rng_10s
首先,若 或 ,答案显然为 No 。
否则,有 ,若 ,答案显然为 Yes 。
考虑 的情况,此时,可能出现无解的情况一定是当前剩余物品 数不足 ,但多余 时进行购买的情况。在模 意义下考虑 的变化,只有进行 操作时会改变 模 的值。我们需要判断的,即为对于通过 操作可以使得 的 ,是否都满足 ,其中 表示 的最小的模 余 的数。
显然, 越大越有可能不满足上述不等式,因此判断可以取到的最大的 对应的不等式是否成立即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
bool work(ll a, ll b, ll c, ll d) {
if (b > a || b > d) return false;
if (b <= c) return true;
ll g = __gcd(b, d);
return b - ((g - a % g == 0) ? g : (g - a % g)) <= c;
}
int main() {
int T; read(T);
while (T--) {
ll a, b, c, d;
read(a), read(b), read(c), read(d);
if (work(a, b, c, d)) puts("Yes");
else puts("No");
}
return 0;
}
Problem C. String Coloring
搜索字符串前 个字符的颜色,由题设,我们已经可以知道两种颜色各自拼接后的结果。
进行简单 dp 计算满足拼接结果的后 个字符的涂色方案数即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 40;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN], a[MAXN], b[MAXN];
int n, dp[MAXN][MAXN]; ll ans;
void work(int pos, int la, int lb) {
if (pos == n) {
memset(dp, 0, sizeof(dp)), dp[n][la] = 1;
for (int i = n; i >= 1; i--)
for (int j = max(0, i - lb), k = i - j; j <= la && j <= i; j++, k--) {
if (s[i] == a[j]) dp[i - 1][j - 1] += dp[i][j];
if (s[i] == b[k]) dp[i - 1][j] += dp[i][j];
}
ans += dp[0][0];
return;
}
a[la + 1] = s[pos];
work(pos - 1, la + 1, lb);
b[lb + 1] = s[pos];
work(pos - 1, la, lb + 1);
}
int main() {
read(n), scanf("%s", s + 1);
work(n * 2, 0, 0);
writeln(ans);
return 0;
}
Problem D. Histogram Coloring
首先可以考虑删去所有不在任意一个 的正方形中的方格,每删去一个,便将答案 。
考虑用 的正方形的样式代替 的正方形,来表示剩余部分。
考虑一些显然的性质, \array{\array{0,0}\\array{1,1}} 的上下必然是 \array{\array{1,1}\\array{0,0}}, \array{\array{1,0}\\array{1,0}} 的左右必然是 \array{\array{0,1}\\array{0,1}} 。
因此,我们称存在 \array{\array{0,0}\\array{1,1}} 的列是一个关键的列,考虑计算 表示第 列是关键列时,前 列的涂色方式数,可以通过枚举下一个关键列的方式转移。
关于转移系数的计算,可以注意到,对于不存在 \array{\array{0,0}\\array{1,1}} 或 \array{\array{1,1}\\array{0,0}} 的一行,其上方一行的涂色方式恰好有 种,直接计算可以自由决定的行数 , 即为转移系数。
时间复杂度 ,可简单优化至 。
官方题解中介绍了一种 的做法,本质上可以看做是用笛卡尔树的思想对该算法的优化。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int power(int x, ll y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
ll cnt; int dp[MAXN];
int n, ans, a[MAXN], b[MAXN];
int main() {
read(n), ans = 1;
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] == 1) {
cnt += 1;
a[i] = 0;
}
}
for (int i = 1; i <= n; i++) {
int Max = max(a[i - 1], a[i + 1]);
if (a[i] > Max) {
cnt += a[i] - Max;
a[i] = Max;
}
}
ans = power(2, cnt);
for (int i = 1; i <= n - 1; i++) {
b[i] = min(a[i], a[i + 1]);
if (b[i]) b[i]--;
}
dp[0] = 1;
for (int i = 0; i <= n - 1; i++) {
for (int j = i + 1; j <= n; j++) {
static int c[MAXN];
memset(c, 0, sizeof(c));
for (int k = i + 1, Min = min(b[k], b[i]); k <= j - 1; k++, chkmin(Min, b[k]))
chkmax(c[k], Min);
for (int k = j - 1, Min = min(b[k], b[j]); k >= i + 1; k--, chkmin(Min, b[k]))
chkmax(c[k], Min);
ll cnt = 0;
for (int k = i + 1; k <= j - 1; k++) {
c[k] = b[k] - c[k];
if (c[k] - c[k - 1] > 0) cnt += c[k] - c[k - 1];
}
if (b[i] == 0 && (b[j] != 0 || i != j - 1)) cnt++;
update(dp[j], 1ll * dp[i] * power(2, cnt) % P);
if (b[j] == 0) break;
}
}
writeln(1ll * ans * dp[n] % P);
return 0;
}
Problem E. Synchronized Subsequence
将字符串尽可能地分割,使得在保证同一组字符均在每一段中的情况下,字符串被分成的段数尽量多。
注意到我们需要最大化字典序,对于字符串 ,分别最大化 和 的字典序即可最大化 的字典序,因此,可以考虑分别计算每一段的答案,再通过简单 dp 合并答案。
完成分割后,可以发现,同一段内的每一组字符的先后顺序一定是固定的,否则一定可以细分。
对于先后顺序是 ab 的段,最优的方案显然是贪心地选取尽可能多的 ab ,使得不存在连续的 a 。
对于先后顺序是 ba 的段,考虑枚举第一对选的字符 ,由分段的性质,可以贪心地发现选取 后的每一对字符得到的结果一定是最优的,因此枚举后比较即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 6005;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN];
int n, tot, rk[MAXN];
string res[MAXN], dp[MAXN];
int la, lb, a[MAXN], b[MAXN];
int main() {
read(n), scanf("\n%s", s + 1);
for (int i = 1; i <= 2 * n; i++)
if (s[i] == 'a') a[++la] = i, rk[i] = la;
else b[++lb] = i, rk[i] = lb;
int last = 0;
for (int i = 1; i <= n; i++) {
if (i == n || max(a[i], b[i]) < min(a[i + 1], b[i + 1])) {
if (a[i] < b[i]) {
res[++tot] = ""; int Max = 0;
for (int j = last + 1; j <= i; j++)
if (a[j] > Max) {
res[tot] += "ab";
Max = b[j];
}
} else {
res[++tot] = "";
for (int j = last + 1; j <= i; j++) {
string tmp = "";
for (int k = last * 2 + 1; k <= i * 2; k++)
if (rk[k] >= j) tmp += s[k];
chkmax(res[tot], tmp);
}
}
last = i;
}
}
for (int i = tot; i >= 1; i--)
dp[i] = max(dp[i + 1], res[i] + dp[i + 1]);
cout << dp[1] << endl;
return 0;
}
Problem F. Manju Game
记数列奇数位的数和为 ,偶数位的数和为 。
考虑 为偶数的情况,此时,先手初始时走 和 处可以分别保证得到 分和 分,因此先手得分不低于 ;同时后手存在策略将先手得分控制在 以内,因此 为偶数时先手得分为 。
对于 为奇数的情况 ,首先,若先手初始时走 处,可以保证得到 分,并且,走在其余奇数位置,后手能够保证先手的得分不超过 。因此,先手想要得到多于 分,必须初始时走在偶数位置。
考虑此时游戏的进程:
在先手玩家的回合,先手玩家可能选择得到
分,结束游戏;或者走在某个偶数位置。
此时,后手玩家可以选择该位置的一侧,先手玩家得到该侧的
分,游戏在另一侧继续。
那么,先手玩家的决策可以写作一棵二叉树的形式,区间 对应的根节点表示先手玩家走在的偶数位置,若不存在,则表示先手玩家选择得到 分,结束游戏。左右子树对应了不同的后手玩家的决策。
不难发现,对于给定的决策树,后手玩家可以决定游戏结束的子树,因此,树的结构并不重要,先手玩家需要最大化所有结束区间的 的最小值。
二分答案后可以通过前缀和简单判断。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, w, b, a[MAXN], s[MAXN];
bool check(int mid) {
int Min = 0;
for (int i = 2; i <= n; i += 2)
if (s[i - 1] - Min >= mid) chkmin(Min, s[i]);
return s[n] - Min >= mid;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(a[i]);
if (i & 1) b += a[i], s[i] = s[i - 1] + a[i];
else w += a[i], s[i] = s[i - 1] - a[i];
}
if (n % 2 == 0) {
printf("%d %d\n", max(w, b), min(w, b));
return 0;
}
int l = -w - b, r = w + b;
while (l < r) {
int mid = (l + r + 2 * w + 2 * b + 1) / 2 - w - b;
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d %d\n", w + l, b - l);
return 0;
}