AtCoder Grand Contest 005

题目传送门:AtCoder Grand Contest 005

A - STring

括号匹配。

#include <cstdio>

const int MN = 200005;

char s[MN], t[MN];
int tp;

int main() {
	scanf("%s", s + 1);
	for (int i = 1; s[i]; ++i) {
		if (tp && t[tp] == 'S' && s[i] == 'T') --tp;
		else t[++tp] = s[i];
	}
	printf("%d\n", tp);
	return 0;
}

B - Minimum Sum

单调栈裸题。

#include <cstdio>

typedef long long LL;
const int MN = 200005;

int N, A[MN], L[MN], R[MN];
int stk[MN], tp;
LL Ans;

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
	stk[tp = 1] = 0;
	for (int i = 1; i <= N; ++i) {
		while (A[stk[tp]] > A[i]) R[stk[tp--]] = i;
		L[i] = stk[tp], stk[++tp] = i;
	}
	while (tp) R[stk[tp--]] = N + 1;
	for (int i = 1; i <= N; ++i) Ans += (LL)(i - L[i]) * (R[i] - i) * A[i];
	printf("%lld\n", Ans);
	return 0;
}

C - Tree Restoring

考虑确定一条直径,然后在上面构造。

#include <cstdio>
#include <algorithm>

const int MN = 105;

int N, A[MN], B[MN];

int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]), ++B[A[i]];
	std::sort(A + 1, A + N + 1);
	int len = A[N];
	for (int i = 0; i <= len; ++i) --B[std::max(len - i, i)];
	for (int i = 1; i <= N; ++i) if (B[i] < 0) return puts("Impossible"), 0;
	int hlen = (len + 1) / 2;
	for (int i = 1; i <= hlen; ++i) if (B[i]) return puts("Impossible"), 0;
	puts("Possible");
	return 0;
}

D - ~K Perm Counting

建立一张 \(N \times N\) 的二分图,左侧的 \(i\) 与右侧的 \(a_i\) 连边。那么答案就是不存在「禁忌边」的完美匹配方案数。

此处「禁忌边」就是指左侧的 \(i\) 与右侧的 \(i - K\)\(i + K\) 之间的边。

我们考虑容斥,答案就是自由匹配的方案数,减去钦定 \(1\) 个位置必须匹配禁忌边的方案数,加上钦定 \(2\) 个,以此类推。

考虑禁忌边构成的图的结构,容易发现恰好由 \(2 K\) 条链构成。要求的即是在此上钦定 \(i\) 条边匹配的方案数,\(\mathcal O (N^2)\) DP 是显然的。

#include <cstdio>

typedef long long LL;
const int Mod = 924844033;
const int MN = 2005;

int N, K;
int A[MN * 2], M;
int f[2][2][MN];

int main() {
	scanf("%d%d", &N, &K);
	for (int i = 1; i <= K; ++i) {
		for (int j = i; j <= N; j += K) A[++M] = j != i;
		for (int j = i; j <= N; j += K) A[++M] = j != i;
	}
	int o = 0;
	f[0][0][0] = 1;
	for (int i = 1; i <= M; ++i) {
		o ^= 1;
		for (int j = 0; j <= N; ++j) {
			f[o][0][j] = f[o][1][j] = 0;
			f[o][0][j] = (f[o ^ 1][0][j] + f[o ^ 1][1][j]) % Mod;
			if (A[i] && j)
				f[o][1][j] = f[o ^ 1][0][j - 1];
		}
	}
	int Coef = 1, Ans = 0;
	for (int i = N; i >= 0; --i) {
		Ans = (Ans + (i & 1 ? -1ll : 1ll) * Coef * (f[o][0][i] + f[o][1][i])) % Mod;
		Coef = (LL)Coef * (N - i + 1) % Mod;
	}
	printf("%d\n", (Ans + Mod) % Mod);
	return 0;
}

E - Sugigma: The Showdown

我们考虑一种 Sigma 可以永远地逃掉的情况:

Sigma 到达了一条红边的两端点之一,这条红边的两端点在蓝树上的距离大于等于 \(3\),且 Sugim 下一步无法直接抓到 Sigma。

此时 Sigma 就可以每回合都呆在离 Sugim 的位置较远的端点上,永远不会被抓到,而且如果距离等于 \(1\)\(2\) 的话这是不成立的。

我们把这样的边的端点做一个标记,表示这是对 Sigma 的安全点。

接下来我们考虑以 \(Y\) 为根的蓝树,Sigma 想要一直沿着红边逃跑,Sugim 只能沿着蓝色树边追赶。

注意我们已经删掉所有在蓝树上距离大于等于 \(3\) 的红边了,现在剩下的只有距离为 \(1\)\(2\) 的红边。

如果 Sigma 逃到了有标记的点,那么他就胜利。否则他应该坚持得久一点,答案就是坚持的回合数乘 \(2\)(此时回合指一来一回)。

要弄清楚 Sigma 的策略,首先必须得弄清楚 Sugim 的策略。

注意到 Sigma 走的红边,因为距离最多为 \(2\),所以是不可能在某次操作时从 Sugim 头上跳过去的,因为一旦跳过去了,下一步就会被抓,还不如不动。

所以很显然 Sugim 的策略就是,一直往 Sigma 所在的子树方向走。

而 Sigma 的应对方法就是,他第 \(i\) 步走到的点 \(x_i\)\(x_0 = X\))到根 \(Y\) 的距离,必须大于 \(i\),这样无论如何也不会被抓到。

在保持路途中不会被抓到的前提下,Sigma 应该躲在到根 \(Y\) 最深的能到达的节点上,等待 Sugim 一步一步追过来结束游戏。

当然如果发现保持路途中不会被抓到时能够到达有标记的点就直接输出 \(-1\) 就行了。

#include <cstdio>
#include <vector>

const int MN = 200005;

int N, X, Y, Ans;
std::vector<int> G[2][MN];

int par[MN], dep[MN];
void DFS0(int u, int p) {
	dep[u] = dep[par[u] = p] + 1;
	for (int v : G[1][u]) if (v != p) DFS0(v, u);
}

void DFS1(int u, int p, int d) {
	int win = 0;
	Ans = std::max(Ans, dep[u]);
	for (int v : G[0][u]) if (v != p) {
		if (u == par[v] || v == par[u] || u == par[par[v]] || v == par[par[u]] || par[u] == par[v]) {
			if (d + 1 < dep[v]) DFS1(v, u, d + 1);
		} else { win = 1; break; }
	}
	if (win) Ans = N;
}

int main() {
	scanf("%d%d%d", &N, &X, &Y);
	for (int o = 0; o < 2; ++o)
		for (int i = 1, x, y; i < N; ++i)
			scanf("%d%d", &x, &y),
			G[o][x].push_back(y),
			G[o][y].push_back(x);
	dep[0] = -1, DFS0(Y, 0);
	DFS1(X, 0, 0);
	printf("%d\n", Ans == N ? -1 : 2 * Ans);
	return 0;
}

F - Many Easy Problems

对于某个点 \(u\) 它对 \(K\) 的答案的贡献就是,总方案数减去 \(K\) 个点全在 \(u\) 的某个子树中的方案数。

我们可以 DFS 一下对所有的 \(u\) 求出它的每个子树的大小,因为每个大小本质上也没什么区别,直接丢进一个桶里。

\(K\) 的答案反正就是 \(\displaystyle N \binom{N}{K}\) 减去 \(\displaystyle \sum b_i \binom{i}{K}\),此处 \(b_i\) 就是大小为 \(i\) 的子树个数。

显然是卷积的形式,NTT 优化即可,\(924844033\) 的原根是 \(5\)

#include <cstdio>

inline void swap(int &x, int &y) { x ^= y ^= x ^= y; }

typedef long long LL;
const int MN = 200005;
const int MS = 524288;
const int Mod = 924844033;
const int G = 5, iG = 554906420;

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

int Inv[MN], Fac[MN], iFac[MN];
int R[MS];

void Init(int N) {
	Inv[1] = 1;
	for (int i = 2; i <= N; ++i) {
		Inv[i] = (LL)(Mod - Mod / i) * Inv[Mod % i] % Mod;
	}
	Fac[0] = iFac[0] = 1;
	for (int i = 1; i <= N; ++i) {
		Fac[i] = (LL)Fac[i - 1] * i % Mod;
		iFac[i] = (LL)iFac[i - 1] * Inv[i] % Mod;
	}
}

void FNTT(int *A, int Sz, int Ty) {
	for (int i = 0; i < Sz; ++i)
		if (R[i] < i) swap(A[R[i]], A[i]);
	for (int j = 1; j < Sz; j <<= 1) {
		int j2 = j << 1, wn = qPow(~Ty ? G : iG, (Mod - 1) / j2);
		for (int i = 0; i < Sz; i += j2) {
			for (int k = 0, w = 1; k < j; ++k, w = (LL)w * wn % Mod) {
				int X = A[i + k], Y = (LL)w * A[i + j + k] % Mod;
				A[i + k] = X + Y; if (A[i + k] >= Mod) A[i + k] -= Mod;
				A[i + j + k] = X - Y; if (A[i + j + k] < 0) A[i + j + k] += Mod;
			}
		}
	}
	if (Ty == -1) {
		int InvSz = qPow(Sz, Mod - 2);
		for (int i = 0; i < Sz; ++i) A[i] = (LL)A[i] * InvSz % Mod;
	}
}

int N, A[MS], B[MS];
int h[MN], nxt[MN * 2], to[MN * 2], tot;
inline void ins(int x, int y) {
	nxt[++tot] = h[x], to[tot] = y, h[x] = tot;
}

int DFS(int u, int f) {
	int sz = 1, sv;
	for (int i = h[u]; i; i = nxt[i]) if (to[i] != f) {
		sz += sv = DFS(to[i], u);
		--A[sv], --A[N - sv];
	} return sz;
}

int main() {
	scanf("%d", &N);
	Init(N);
	for (int i = 1, x, y; i < N; ++i) {
		scanf("%d%d", &x, &y);
		ins(x, y), ins(y, x);
	}
	DFS(1, 0), A[N] = N;
	for (int i = 1; i <= N; ++i) A[i] = ((LL)A[i] * Fac[i] % Mod + Mod) % Mod;
	for (int i = 0; i < N; ++i) B[i] = iFac[N - 1 - i];
	int Sz = 1, Bt = 0;
	for (; Sz < N + N; Sz <<= 1, ++Bt) ;
	for (int i = 0; i < Sz; ++i) R[i] = R[i >> 1] >> 1 | (i & 1) << (Bt - 1);
	FNTT(A, Sz, 1), FNTT(B, Sz, 1);
	for (int i = 0; i < Sz; ++i) A[i] = (LL)A[i] * B[i] % Mod;
	FNTT(A, Sz, -1);
	for (int i = 1; i <= N; ++i) printf("%lld\n", (LL)A[i + N - 1] * iFac[i] % Mod);
	return 0;
}
posted @ 2020-06-22 23:22  粉兔  阅读(247)  评论(0编辑  收藏  举报