AtCoder Grand Contest 002

题目传送门:AtCoder Grand Contest 002

A - Range Product

略。

#include <cstdio>

int A, B;

int main() {
	scanf("%d%d", &A, &B);
	if (A <= 0 && B >= 0) puts("Zero");
	else if (A > 0 || (B - A) & 1) puts("Positive");
	else puts("Negative");
	return 0;
}

B - Box and Ball

在任意时刻,维护 \(\mathrm{num}[i]\) 表示第 \(i\) 个盒子内的球数,\(\mathrm{typ}[i]\) 表示第 \(i\) 个盒子内是否有红球:\(0\) 一定没有,\(1\) 一定有,\(2\) 两种都有可能。

转移显然,最终 \(\mathrm{typ}\) 非零的位置的个数就是答案。

#include <cstdio>

const int MN = 100005;

int N, M, Ans;
int num[MN], typ[MN];

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; ++i)
		num[i] = 1, typ[i] = i == 1;
	for (int i = 1; i <= M; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		if (num[x] == 1) {
			if (typ[x] == 1) typ[y] = 1;
			else if (typ[x] == 2) typ[y] = 2;
			typ[x] = 0;
		} else if (typ[x] != 0) typ[x] = typ[y] = 2;
		--num[x], ++num[y];
	}
	for (int i = 1; i <= N; ++i) if (typ[i]) ++Ans;
	printf("%d\n", Ans);
	return 0;
}

C - Knot Puzzle

把操作过程倒过来,相当于每次合并两个相邻的,需要保证合并后的长度至少为 \(L\)

如果任意两个相邻的长度之和都比 \(L\) 小的话那一定无解,否则第一次合并那两个然后一直向左向右合并完即可。注意倒序输出。

#include <cstdio>

const int MN = 100005;

int N, L, A[MN];

int main() {
	scanf("%d%d", &N, &L);
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
	int ok = 0;
	for (int i = 1; i < N; ++i) if (A[i] + A[i + 1] >= L) ok = i;
	if (!ok) return puts("Impossible"), 0;
	puts("Possible");
	for (int i = 1; i < ok; ++i) printf("%d\n", i);
	for (int i = N - 1; i >= ok; --i) printf("%d\n", i);
	return 0;
}

D - Stamp Rally

对询问做整体二分,用可撤销并查集维护连通性以及连通块大小,时间复杂度为 \(\mathcal O ((M + Q) \log M \log N)\)

#include <cstdio>
#include <algorithm>

const int MN = 100005, MM = 100005, MQ = 100005;

int N, M, a[MM], b[MM];
int Q, x[MQ], y[MQ], z[MQ], p[MQ], ans[MQ];

int par[MN], siz[MN], stk[MN], tp;
inline int fp(int u) {
	while (par[u]) u = par[u];
	return u;
}
inline void merge(int u, int v) {
	u = fp(u), v = fp(v);
	if (u == v) return ;
	if (siz[u] < siz[v]) std::swap(u, v);
	par[v] = u, siz[u] += siz[v];
	stk[++tp] = v;
}
inline void undo(int k) {
	while (tp > k) {
		int v = stk[tp--];
		siz[par[v]] -= siz[v];
		par[v] = 0;
	}
}

int tmp[MQ];
void Solve(int l, int r, int s, int t) {
	if (s > t) {
		for (int i = l; i <= r; ++i) merge(a[i], b[i]);
		return ;
	}
	if (l == r) {
		for (int i = s; i <= t; ++i) ans[p[i]] = l;
		merge(a[l], b[l]);
		return ;
	}
	int mid = (l + r) >> 1, now = tp;
	for (int i = l; i <= mid; ++i) merge(a[i], b[i]);
	int s_ = s, t_ = t;
	for (int i = s; i <= t; ++i) {
		int u = fp(x[p[i]]), v = fp(y[p[i]]);
		int sum = u == v ? siz[u] : siz[u] + siz[v];
		if (sum >= z[p[i]]) tmp[s_++] = p[i];
		else tmp[t_--] = p[i];
	}
	undo(now);
	for (int i = s; i <= t; ++i) p[i] = tmp[i];
	Solve(l, mid, s, t_);
	Solve(mid + 1, r, s_, t);
}

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= M; ++i) scanf("%d%d", &a[i], &b[i]);
	scanf("%d", &Q);
	for (int i = 1; i <= Q; ++i) scanf("%d%d%d", &x[i], &y[i], &z[i]), p[i] = i;
	for (int i = 1; i <= N; ++i) siz[i] = 1;
	Solve(1, M, 1, Q);
	for (int i = 1; i <= Q; ++i) printf("%d\n", ans[i]);
	return 0;
}

E - Candy Piles

每次操作删除最大的一堆,或者把所有堆的石子数量减一。

如果把 \(a\) 数组从小到大排序,然后把每一堆的石子摞起来,就像这样:

那么每次操作就相当于删除最右侧一列或最底部一行,删光了的输掉游戏。

那么就相当于初始时棋子在右下角,每次能向上或向左一格,谁走出了边界谁就输了。

我们直接用博弈论中最简单的 PN 态转换来做:P 态(Previous)就是先手必败态,N 态(Next)就是先手必胜态。

我们规定浅粉色区域(边界外)都是 N 态。所有转移都只能到 N 态的就是 P 态,只要有一种转移能到 P 态的就是 N 态。

染完色就是这样(金色是 P 态,粉色是 N 态):

我们无法求出每个格子的状态,但是观察发现:对于每一列,不存在连续两个相邻的 P 态,不存在三个相邻的 N 态。

而且我们还发现,对于相邻两列,左边一列的颜色向下移动一格,就与右边一列的颜色完全一致。

这两个结论读者可以自行证明,因为都不难这里不再赘述。

我们考虑从左向右按列递推,假设当前是第 \(i\) 列,前一列的高度为 \(a_{i - 1}\),这一列的高度为 \(a_i\)

我们记录每一列的,连续两个 N 态的位置,如上图倒数第二列就是 \(\{1, 4\}\) 这两个位置,最后一列就是 \(\{3, 6\}\) 这两个位置。

但是这些位置一直在变化,但是可以发现:基本上都是每次减 \(1\),然后加入或删除一些位置。

那么我们可以存储这些位置加上 \(i\) 的值,比如此时位置为 \(p\),存储 \(p + i\),到下一列时位置即为 \((p + i) - (i + 1) = (p - 1)\)

接下来处理加入新的一列 \(a_i\) 时会发生什么:

如果 \(a_{i - 1} = a_i\),则仅仅是左边的状态向下移动了一格,然后在最顶上加入一个 P 态而已。否则 \(a_{i - 1} < a_i\)

对于 \(a_i\) 超出 \(a_{i - 1}\) 的部分,是从第 \(a_i\) 高度为 P 态开始,往下依次为 PN 态交替,这一部分没有连续两个 N 态。

那么考虑高度为 \(a_{i - 1} + 1\) 时的状态:它会和前一列的高度为 \(a_{i - 1}\) 的状态共同影响该列的高度为 \(a_{i - 1}\) 的状态。

这可以直接通过 \(a_i - a_{i - 1}\) 的奇偶性算出。如果它的状态和前一列的高度为 \(a_{i - 1}\) 的状态相同,则不会产生任何相邻的 N 态。否则:

如果它的状态是 P,前一列状态是 N,比如上图中高度为 \([6, 7]\) 的相邻两列,就会在 \(a_{i - 1} - 1\) 高度处产生相邻的 N 态。

如果它的状态是 N,前一列状态是 P,比如上图中高度为 \([4, 6]\) 的相邻两列,就会在 \(a_{i - 1}\) 高度处产生相邻的 N 态。

也就是说,每次只会加入最多一个新的相邻的 N 态,而且高度总是比之前的高。

那么我们可以用一个队列来维护这些连续的 N 态的位置:每次加入新的,如果最低的那个低于高度 \(1\) 了就把它去掉。

再来一个变量 \(\mathrm{lstc}\) 表示高度为 \(a_i\) 时的状态,代码中 \(0\) 表示 P 态,\(1\) 表示 N 态。就可以实现转移和求最终答案了。

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

#include <cstdio>
#include <algorithm>

const int MN = 100005;

int N, A[MN];
int que[MN], l, r;

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
	std::sort(A + 1, A + N + 1);
	l = 1, r = 0;
	int lstc = 0;
	for (int i = 2; i <= N; ++i) {
		if (A[i] == A[i - 1]) lstc ^= 1;
		else {
			int c = ~(A[i] - A[i - 1]) & 1;
			if (c != lstc) que[++r] = A[i - 1] - lstc + i;
			lstc = 0;
		}
		while (l <= r && que[l] <= i) ++l;
	}
	puts(lstc ^ (A[N] & 1) ^ ((r - l) & 1) ? "First" : "Second");
	return 0;
}

F - Leftmost Ball

我们直接对最终的那个产生出的序列进行计数。

如果 \(N = 1\)\(K = 1\),直接输出 \(1\) 即可。

那么现在序列中至少有 \(1\)\(2\) 两个非零元素了。

可以发现把序列中非零的数的值做一个置换(也就是 \(1\)\(2\)\(2\)\(3\)\(3\)\(1\) 这种),序列仍然合法。

那么我们强制计数字典序最小的那种序列的个数,最后把答案乘以 \(N!\) 输出即可。

也就是说,必须先出现 \(1\) 再出现 \(2\),先出现 \(2\) 再出现 \(3\),以此类推。

再考虑关于 \(0\) 的合法性:\(1\) 必须在第一个 \(0\) 之后出现,\(2\) 必须在第二个 \(0\) 之后出现,以此类推。

也就是:记第 \(i\)\(0\) 的位置为 \(p_i\),以及 \(i\) 的第一次出现位置为 \(q_i\)。有:

  1. \(p_{i - 1} < p_i\)
  2. \(q_{i - 1} < q_i\)
  3. \(p_i < q_i\)

当然还有对于每个颜色 \(i \in [1, N]\),它的后 \(K - 2\) 次出现,必须按照顺序。

似乎都是废话,但是其实此时我们已经把问题转化成了,求一张图的拓扑排序的数量的问题:

上图中,有 \(K\) 行,和 \(N\) 列。

注意到第一个点必须是最左边的 \(0\),然后接下来可以选第二个 \(0\) 或者最上面的 \(1\)

如果选了最上面的 \(1\),那下面那条长度为 \(K - 2\) 的跟着的链可以直接以组合数的形式乘到答案里,然后删掉,变成少第一列的情况。

如果选了第二个 \(0\),又变成能选 \(1\) 或第三个 \(0\)。如果此时选了 \(1\),就变成能选 \(2\) 或第三个 \(0\) 的情况。以此类推。

可以发现这个结构是可以 DP 的。

\(\mathrm{f}[i][j]\) 表示目前 \(0\) 剩下靠后的 \(i\) 个,有颜色的点剩下靠后的 \(j\) 列时的方案数。此时必须有 \(j \ge i\)。转移显然:

\[\mathrm{f}[i][j] = \mathrm{f}[i - 1][j] + \binom{i + j \cdot (K - 1) - 1}{K - 2} \cdot \mathrm{f}[i][j - 1] \]

答案即为 \(\mathrm{f}[N][N]\),时间复杂度为 \(\mathcal O (NK + N^2)\)

#include <cstdio>

typedef long long LL;
const int Mod = 1000000007;
const int MN = 2005, MS = 4000005;

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[MS], iFac[MS];
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;
}
inline int Binom(int N, int M) {
	return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
}

int N, K;
int f[MN][MN];

int main() {
	scanf("%d%d", &N, &K);
	if (K == 1 || N == 1) return puts("1"), 0;
	Init(N * K);
	f[0][0] = 1;
	for (int j = 1; j <= N; ++j) f[0][j] = (LL)f[0][j - 1] * Binom(j * (K - 1) - 1, K - 2) % Mod;
	for (int i = 1; i <= N; ++i) {
		f[i][i] = f[i - 1][i];
		for (int j = i + 1; j <= N; ++j)
			f[i][j] = (f[i - 1][j] + (LL)f[i][j - 1] * Binom(i + j * (K - 1) - 1, K - 2)) % Mod;
	}
	printf("%lld\n", (LL)f[N][N] * Fac[N] % Mod);
	return 0;
}
posted @ 2020-06-07 05:03  粉兔  阅读(60)  评论(0编辑  收藏