AtCoder Grand Contest 045

题目传送门:AtCoder Grand Contest 045

A - Xor Battle

从后往前分析。令从位置 \(i\) 开始游戏时,能让 \(0\) 号玩家获胜的初始数值的集合为 \(X_i\)

\(X_{N + 1} = \{0\}\)。如果 \(S_i = \mathtt{0}\)\(X_i = X_{i + 1} \oplus A_i\);否则如果 \(A_i \notin X_{i + 1}\)\(X_i = \varnothing\) 否则 \(X_i = X_{i + 1}\)

用异或线性基维护 \(X\) 即可。

#include <cstdio>

typedef long long LL;
const int MN = 205;

int N;
LL A[MN];
char s[MN];
LL B[64];

int main() {
	int Tests;
	scanf("%d", &Tests);
	while (Tests--) {
		scanf("%d", &N);
		for (int i = 1; i <= N; ++i) scanf("%lld", &A[i]);
		scanf("%s", s + 1);
		int ok = 1;
		for (int j = 0; j < 60; ++j) B[j] = 0;
		for (int i = N; i >= 1; --i) {
			if (s[i] == '1') {
				LL x = A[i];
				for (int j = 59; j >= 0; --j) if (x >> j & 1) {
					if (B[j]) x ^= B[j];
					else break;
				}
				if (x) { ok = 0; break; }
			} else {
				LL x = A[i];
				for (int j = 59; j >= 0; --j) if (x >> j & 1) {
					if (!B[j]) B[j] = x;
					x ^= B[j];
				}
			}
		}
		puts(ok ? "0" : "1");
	}
	return 0;
}

B - 01 Unbalanced

我们把 0 看成 \(+1\),把 1 看成 \(-1\)。那么就是要让 \(0 \sim N\) 之间所有位置的前缀和的极差最小。

如果我们强制规定前缀和必须大于等于某个值 \(M\)(前提是存在方案)那么这就相当于固定了前缀和最小值,让前缀和最大值尽量小。

这可以通过很简单的贪心实现:在不违反限制的情况下,从前到后尽量把每个 ? 都变成 1,迫不得已时才变成 0

这样枚举一波就可以做到 \(\mathcal O (N^2)\),但是它 TLE 了。

同时我们可以求出这个 \(M\) 最大能到多少合法(把所有 ? 看作 0 后的前缀和最小值),记作 \(Z\),不过目前它没用。

注意到当 \(M\) 增加 \(2\) 时,最大值也最多增加 \(2\),或者也有可能不变(把第一个没变成 0? 变成 0 即可)。

那么也就是 \(M\) 越大越好,但是因为这东西是和奇偶性相关的,所以要把 \(Z\)\(Z - 1\) 都进行一下 check。

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

#include <cstdio>
#include <cstring>
#include <algorithm>

const int MN = 1000005;

int N;
char S[MN];

int p[MN];
inline int Solve(int lb) {
	p[N] = 0;
	for (int i = N; i >= 1; --i)
		p[i - 1] = std::max(p[i] + (S[i] == '1' ? 1 : -1), 0);
	int maxval = 0;
	for (int i = 1, now = 0; i <= N; maxval = std::max(maxval, now), ++i)
		if (S[i] == '?') now += now - 1 - p[i] >= lb ? -1 : 1;
		else now += S[i] == '0' ? 1 : -1;
	return maxval;
}

int main() {
	scanf("%s", S + 1), N = strlen(S + 1);
	int minval = 0;
	for (int i = 1, now = 0; i <= N; ++i)
		if (S[i] == '1') minval = std::min(minval, --now);
		else ++now;
	printf("%d\n", std::min(Solve(minval) - minval, Solve(minval - 1) - minval + 1));
	return 0;
}

C - Range Set

显然 01 没有区别,那么不妨假设 \(A \le B\)。考虑如何判断一个序列是否合法。

把操作倒过来考虑,也就是每次选一段连续的 \(A\)0 或者连续的 \(B\)1 变成任意字符,问能否变成全 0 串。

显然如果最终序列中甚至不存在上述子串,一定不合法。但是还有其它要求:

试想如果存在连续的 \(B\)1,注意到 \(A \le B\),这样的串实际上是一定合法的。

为什么?现在进行正向操作,从左到右填充那一段左边的字符,再从右到左填充那一段右边的字符,操作时互不干扰,只会影响到那一段本身,不会跨过去,最后再把那一段都变成 1 即可。

但是如果只存在连续的 \(A\)0 却不一定合法,比如当 \(A = 2\)\(B = 4\) 时的 0010

倒序操作,此时我们需要把连续的 \(A\)0 变成任意字符,但是只这么做是不行的——你消不掉那些 1

所以必须要至少做一次把连续的 \(B\)1 变成任意字符的操作,然而我们已经说明了:只要可以做这样的操作,原串就一定合法。

为了「尽量地满足」这个条件,最好的方法就是把所有连续的,长度 \(\ge A\)0 全都变成 1,如果此时存在连续的 \(B\)1 就合法。

综上所述,条件就是:把所有连续的长度 \(\ge A\)0 全都变成 1 之后,存在连续的 \(B\)1

我们用全部 \(2^{N}\) 个串减去不合法的就是答案,而不合法的串就是:

由交替的「长度 \(< A\)0」和「长度 \(< B\)1,其中可以穿插长度 \(\ge A\)0」构成的串。

有一个坑点就是后者是可以在两端是字符 0 的,但是如果那样就不能和「长度 \(< A\)0」贴在一起,必须放在整个串的两端。

随便 DP 甚至不需要优化就可以做到 \(\mathcal O (N A)\) 的复杂度了。

#include <cstdio>
#include <algorithm>

typedef long long LL;
const int Mod = 1000000007;
const int MN = 5005;

inline int qPow(int b, int e) {
	int a = 1;
	for (; e; e >>= 1, b = (LL)b * b % Mod)
		if (e & 1) a = (LL)a * b % Mod;
	return a;
}

int N, A, B;
int f1[MN], f2[MN], f3[MN], f4[MN];
int g1[MN], g2[MN];

int main() {
	scanf("%d%d%d", &N, &A, &B);
	if (A > B) std::swap(A, B);
	if (A == 1) return printf("%d\n", qPow(2, N)), 0;
	f1[1] = 1;
	for (int i = 2; i < B; ++i) {
		f1[i] = 1;
		for (int j = 1; j <= i - 1; ++j)
			f1[i] = (f1[i] + f2[i - j]) % Mod;
		for (int j = A; j <= i - 1; ++j)
			f2[i] = (f2[i] + f1[i - j]) % Mod;
		for (int j = 1; j <= i - 1; ++j)
			f3[i] = (f3[i] + f4[i - j]) % Mod;
		if (i >= A) f4[i] = 1;
		for (int j = A; j <= i - 1; ++j)
			f4[i] = (f4[i] + f3[i - j]) % Mod;
	}
	for (int i = 1; i <= N; ++i) {
		if (i < B) g1[i] = (f1[i] + f3[i]) % Mod;
		for (int j = 1; j < B && j <= i - 1; ++j)
			g1[i] = (g1[i] + (LL)g2[i - j] * f1[j]) % Mod;
		if (i < A) g2[i] = 1;
		for (int j = 1; j < A && j <= i - 1; ++j)
			g2[i] = (g2[i] + g1[i - j]) % Mod;
	}
	int Ans = (g1[N] + g2[N]) % Mod;
	for (int j = 1; j <= N - 1; ++j)
		Ans = (Ans + (LL)g2[N - j] * f2[j]) % Mod;
	printf("%d\n", (qPow(2, N) - Ans + Mod) % Mod);
	return 0;
}

D - Lamps and Buttons

对于 Snuke 来说,因为排列 \(p\) 是均匀随机的,所以任何位置上的灯都是等价的,题目中的「前 \(A\) 盏灯」也只是为了方便描述。

我们把排列 \(p\) 拆解成若干不相交循环的乘积。

那么 Snuke 的策略很简单:

  1. 随便选取一盏还不知道 \(p_i\) 的值为多少的,现在还亮着的 \(i\) 号灯。那么状态变化的灯的编号为 \(p_i\)
    • 如果不存在这样的灯,则说明所有灭着的灯无法被点亮,输掉游戏。
    • 如果选完之后它自己灭掉了(\(p_i = i\)),那就输掉了游戏。
  2. 容易发现,此时 \(i\) 所在循环长度 \(\ge 2\),可以按顺序依次确定 \(p_{p_i}, p_{p_{p_i}}\) 等等,然后把它们全部点亮。
  3. 如果此时所有灯都被点亮了,Snuke 就赢了,否则回到步骤 1。

为了方便后续转换,我们不妨令步骤 1 中的「随便选取」,变成总是选取编号最小的那一盏灯。

注意:此时 Snuke 的策略已经由随机变成确定性的了,但是他的获胜概率是不变的,那么我们仅需统计哪些排列能让他取胜。

能让 Snuke 取胜的排列必须满足:如果在前 \(A\) 盏灯中,存在着某一盏灯 \(t\) 满足 \(p_t = t\),那么必须在选到它之前赢得游戏。

那么我们令 \(t\) 为满足 \(1 \le t \le A\)\(p_t = t\) 的所有编号中最小的那一个,如果不存在这样的灯则 \(t = A + 1\)

可以发现,需要满足的就是:考察 \(1 \le i < t\) 的所有灯,它们所在的循环必须覆盖了所有 \(A < j \le N\)

换句话说,任取 \(A < j \le N\) 都必须满足其所在循环中至少存在一个满足 \(1 \le i < t\) 的元素 \(i\)

抽象到这一步已经足够我们进入推式子环节了,在 \(2 \sim (A + 1)\) 之间枚举 \(t\) 后,一个能让 Snuke 赢得游戏的排列有以下条件:

  1. 对于 \(1 \le i < t\),满足 \(p_i \ne i\)
  2. 如果 \(t \le A\),满足 \(p_t = t\)
  3. 如果 \(t \le A\),对于 \(t < k \le A\),这部分不需要满足任何特殊条件。
  4. 对于 \(A < j \le N\),满足它所在的循环中至少存在一个满足 \(1 \le i < t\) 的元素 \(i\)

条件 1 先不管,条件 2 形同虚设,首先考虑条件 3 和条件 4:

令满足 \(1 \le i < t\)\(i\) 的个数为 \(a\),令满足 \(A < j \le N\)\(j\) 的个数为 \(b\),令满足 \(t < k \le A\)\(k\) 的个数为 \(c\)

显然有 \(a = t - 1\)\(b = N - A\)\(c = \max(A - t, 0)\),相当于求这样的长度为 \((a + c + b)\) 的置换个数(忽略条件 1):

对于任一编号在 \((a + c + 1) \sim (a + c + b)\) 之间的元素,它所在的置换中必须包含编号在 \(1 \sim a\) 的元素。

\(a\) 个任意置换方案数为 \(a!\),依次加入后 \(b\) 个,必须要插入在已存在的循环内,若当前是第 \(i\) 个,则有 \((a + i - 1)\) 种方案。

最后加入中间的 \(c\) 个,可以接在之前的任意元素后面,也可以自成新的循环,若当前是第 \(i\) 个,则有 \((a + b + i)\) 种方案。

整理一下,总方案数为 \(\displaystyle \frac{a}{a + b} (a + c + b)!\)

然而条件 1 还没被考虑,此时可能会多统计前 \(a\) 个元素中的某一个形成了自环的情况,这是需要被剔除的。

所以我们只要使用容斥原理就行啦,容斥一下得到 \(\displaystyle \sum_{d = 0}^{a} {(-1)}^d \binom{a}{d} \frac{a -d}{a - d + b} (a - d + c + b)!\)

算上前面的枚举 \(t\),直接计算即可得到 \(\mathcal O (N + A^2)\) 的时间复杂度。

#include <cstdio>

typedef long long LL;
const int Mod = 1000000007;
const int MN = 10000005, MA = 5005;

inline int qPow(int b, int e) {
	int a = 1;
	for (; e; e >>= 1, b = (LL)b * b % Mod)
		if (e & 1) a = (LL)a * b % Mod;
	return a;
}

int Fac[MN], iFac[MN], Inv[MN];
inline void Init(int N) {
	Fac[0] = 1;
	for (int i = 1; i <= N; ++i) Fac[i] = (LL)Fac[i - 1] * i % Mod;
	iFac[N] = qPow(Fac[N], Mod - 2);
	for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod; 
	for (int i = 1; i <= N; ++i) Inv[i] = (LL)iFac[i] * Fac[i - 1] % Mod;
}
inline int Binom(int N, int M) {
	return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
}

int N, A;

int main() {
	scanf("%d%d", &N, &A);
	Init(N);
	int Ans = 0;
	for (int t = 2; t <= A + 1; ++t) {
		int a = t - 1, b = N - A, c = t <= A ? A - t : 0;
		for (int d = 0; d <= a; ++d)
			Ans = (Ans + (d & 1 ? -1ll : 1ll) * Binom(a, d) * Fac[a - d + b + c] % Mod * (a - d) % Mod * Inv[a - d + b]) % Mod;
	}
	printf("%d\n", (Ans + Mod) % Mod);
	return 0;
}

E - Fragile Balls

不会。

F - Division into Multiples

不会。

posted @ 2020-06-09 02:00  粉兔  阅读(434)  评论(0编辑  收藏  举报