【AtCoder】AtCoder Grand Contest 026

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Colorful Slimes 2

令输入中每一段连续的数字长度为 X i X_i Xi ,答案显然是 ∑ ⌊ X i 2 ⌋ \sum \lfloor\frac{X_i}{2}\rfloor 2Xi

时间复杂度 O ( N ) O(N) O(N)

#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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

首先,若 B > A B>A B>A B > D B>D B>D ,答案显然为 No 。

否则,有 B ≤ A , B ≤ D B\leq A,B\leq D BA,BD ,若 B ≤ C B\leq C BC ,答案显然为 Yes 。

考虑 B ≤ A , B ≤ D , B > C B\leq A,B\leq D,B>C BA,BD,B>C 的情况,此时,可能出现无解的情况一定是当前剩余物品 X X X 数不足 B B B ,但多余 C C C 时进行购买的情况。在模 B B B 意义下考虑 X X X 的变化,只有进行 + D +D +D 操作时会改变 X X X B B B 的值。我们需要判断的,即为对于通过 + D +D +D 操作可以使得 X ≡ v   ( m o d   B ) X\equiv v\ (mod\ B) Xv (mod B) v v v ,是否都满足 f ( v ) ≥ B f(v)\geq B f(v)B ,其中 f ( v ) f(v) f(v) 表示 > C >C >C 的最小的模 B B B v v v 的数。

显然, v v v 越大越有可能不满足上述不等式,因此判断可以取到的最大的 v v v 对应的不等式是否成立即可。

时间复杂度 O ( T L o g V ) O(TLogV) O(TLogV)

#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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

搜索字符串前 N N N 个字符的颜色,由题设,我们已经可以知道两种颜色各自拼接后的结果。

进行简单 O ( N 2 ) O(N^2) O(N2) dp 计算满足拼接结果的后 N N N 个字符的涂色方案数即可。

时间复杂度 O ( 2 N N 2 ) O(2^NN^2) O(2NN2)

#include
using namespace std;
const int MAXN = 40;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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

首先可以考虑删去所有不在任意一个 2 × 2 2\times 2 2×2 的正方形中的方格,每删去一个,便将答案 × 2 \times 2 ×2

考虑用 2 × 2 2\times 2 2×2 的正方形的样式代替 1 × 1 1\times 1 1×1 的正方形,来表示剩余部分。

考虑一些显然的性质, \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}} 的列是一个关键的列,考虑计算 d p i dp_i dpi 表示第 i i i 列是关键列时,前 i i i 列的涂色方式数,可以通过枚举下一个关键列的方式转移。

关于转移系数的计算,可以注意到,对于不存在 \array{\array{0,0}\\array{1,1}} 或 \array{\array{1,1}\\array{0,0}} 的一行,其上方一行的涂色方式恰好有 2 2 2 种,直接计算可以自由决定的行数 c n t cnt cnt 2 c n t 2^{cnt} 2cnt 即为转移系数。

时间复杂度 O ( N 3 + N 2 L o g V ) O(N^3+N^2LogV) O(N3+N2LogV) ,可简单优化至 O ( N 2 ) O(N^2) O(N2)

官方题解中介绍了一种 O ( N L o g V ) O(NLogV) O(NLogV) 的做法,本质上可以看做是用笛卡尔树的思想对该算法的优化。

#include
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  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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

将字符串尽可能地分割,使得在保证同一组字符均在每一段中的情况下,字符串被分成的段数尽量多。

注意到我们需要最大化字典序,对于字符串 S + T S+T S+T ,分别最大化 S S S T T T 的字典序即可最大化 S + T S+T S+T 的字典序,因此,可以考虑分别计算每一段的答案,再通过简单 dp 合并答案。

完成分割后,可以发现,同一段内的每一组字符的先后顺序一定是固定的,否则一定可以细分。

对于先后顺序是 ab 的段,最优的方案显然是贪心地选取尽可能多的 ab​ ,使得不存在连续的 a 。

对于先后顺序是 ba 的段,考虑枚举第一对选的字符 i i i ,由分段的性质,可以贪心地发现选取 i i i 后的每一对字符得到的结果一定是最优的,因此枚举后比较即可。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include
using namespace std;
const int MAXN = 6005;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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

记数列奇数位的数和为 B B B ,偶数位的数和为 W W W

考虑 N N N 为偶数的情况,此时,先手初始时走 1 1 1 N N N 处可以分别保证得到 B B B 分和 W W W 分,因此先手得分不低于 max ⁡ ( B , W ) \max(B,W) max(B,W) ;同时后手存在策略将先手得分控制在 max ⁡ ( B , W ) \max(B,W) max(B,W) 以内,因此 N N N 为偶数时先手得分为 max ⁡ ( B , W ) \max(B,W) max(B,W)

对于 N N N 为奇数的情况 ,首先,若先手初始时走 1 1 1 处,可以保证得到 B B B 分,并且,走在其余奇数位置,后手能够保证先手的得分不超过 B B B 。因此,先手想要得到多于 B B B 分,必须初始时走在偶数位置。

考虑此时游戏的进程:
在先手玩家的回合,先手玩家可能选择得到 B B B 分,结束游戏;或者走在某个偶数位置。
此时,后手玩家可以选择该位置的一侧,先手玩家得到该侧的 W W W 分,游戏在另一侧继续。

那么,先手玩家的决策可以写作一棵二叉树的形式,区间 [ L , R ] [L,R] [L,R] 对应的根节点表示先手玩家走在的偶数位置,若不存在,则表示先手玩家选择得到 B B B 分,结束游戏。左右子树对应了不同的后手玩家的决策。

不难发现,对于给定的决策树,后手玩家可以决定游戏结束的子树,因此,树的结构并不重要,先手玩家需要最大化所有结束区间的 B − W B-W BW 的最小值。

二分答案后可以通过前缀和简单判断。

时间复杂度 O ( N L o g V ) O(NLogV) O(NLogV)

#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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;
}

你可能感兴趣的:(【AtCoder】AtCoder Grand Contest 026)