只要你跑的够快,锅就追不上你

0%

「CCPC 2019 秦皇岛」部分题解

C. Sakurada Reset

「Gym 102361C」Sakurada Reset(动态规划)

题目描述

给定数列 $A, B$,将数列中的每个子序列都看作一个 $1000$ 进制数,问有多少对 $(x, y)$ 满足 $x$ 是 $A$ 的某个子序列,$y$ 是 $B$ 的某个子序列,并且 $x > y$。本质相同的子序列只算一次,答案对大素数取模。

数据范围:$n, m \le 5000, 1 \le A_i, B_i \le 100$。

思路分析

令 $p_i$ 表示 $A$ 中满足 $j < i$ 且 $A_j = A_i$ 的最大的 $j$,如果不存在则为 $0$;$q_i$ 在 $B$ 中有类似的定义。首先计算长度不等的对数,发现这时一定是 $x$ 的长度比 $y$ 的大。预处理出每个数列中特定长度的本质不同子序列个数,就可以直接计算答案了。转移方程:$f(i, j) = f(i - 1)(j) + f(i - 1)(j - 1) - f(p_i - 1)(j - 1)$。

之后再考虑 $x, y$ 长度相同时的贡献。$x, y$ 必定有一个 lcp,之后的第一个数一定是 $x$ 中的比 $y$ 中的大,然后就可以随便排了。将这三个阶段写成三个状态:$f, g, h$,$f(i, j)$ 表示选到 $A_i, B_j$ 为止都相同的方案数,$g, h$ 类似。那么 $f$ 要求 $A_i = B_j$,$g$ 要求 $A_i > B_j$,$h$ 没有要求; $f, g$ 可以从 $f$ 转移过来,$h$ 可以从 $g, h$ 转移过来。下面列出 $f$ 的转移方程,$g, h$ 类似:

前缀和优化 dp 即可。时间复杂度 $O(\max(n, m)^2)$,注意常数优化。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
namespace __main__ {
5
	const int maxn = 5e3, mod = 998244353;
6
	int n, m, a[maxn + 3], b[maxn + 3];
7
	int f[maxn + 3][maxn + 3], g[maxn + 3][maxn + 3];
8
	int last[maxn + 3], p_a[maxn + 3], p_b[maxn + 3];
9
10
	void read_arr(int a[], int n) {
11
		for (int i = 1; i <= n; i++) {
12
			scanf("%d", &a[i]);
13
		}
14
	}
15
16
	void add(int &x, int y) {
17
		x += y, x < mod ? 0 : x -= mod;
18
	}
19
20
	void sub(int &x, int y) {
21
		x -= y, x < 0 ? x += mod : 0;
22
	}
23
24
	void get_f(int f[maxn + 3][maxn + 3], int p[maxn + 3], int a[maxn + 3], int n) {
25
		memset(last, 0, sizeof(last));
26
		f[0][0] = 1;
27
		for (int i = 1; i <= n; i++) {
28
			memcpy(f[i], f[i - 1], sizeof(f[i][0]) * (i + 1));
29
			for (int j = 1; j <= i; j++) {
30
				add(f[i][j], f[i - 1][j - 1]);
31
				if (last[a[i]]) {
32
					sub(f[i][j], f[last[a[i]] - 1][j - 1]);
33
				}
34
			}
35
			p[i] = last[a[i]];
36
			last[a[i]] = i;
37
		}
38
	}
39
40
	inline int func(int x) {
41
		return x < mod ? x : x - mod;
42
	}
43
44
	int sum(int f[][maxn + 3], int u, int d, int l, int r) {
45
		u--, l--;
46
		int x = f[d][r];
47
		if (~u) sub(x, f[u][r]);
48
		if (~l) sub(x, f[d][l]);
49
		if ((~u) && (~l)) add(x, f[u][l]);
50
		return x;
51
	}
52
53
	int solve() {
54
		f[0][0] = 1, g[0][0] = 0;
55
		for (int i = 1; i <= n; i++) {
56
			f[i][0] = 1, g[i][0] = 0;
57
		}
58
		for (int i = 1; i <= m; i++) {
59
			f[0][i] = 1, g[0][i] = 0;
60
		}
61
		for (int i = 1; i <= n; i++) {
62
			for (int j = 1; j <= m; j++) {
63
				f[i][j] = 0, g[i][j] = 0;
64
				if (a[i] == b[j]) f[i][j] = sum(f, p_a[i], i - 1, p_b[j], j - 1);
65
				if (a[i] > b[j]) g[i][j] = sum(f, p_a[i], i - 1, p_b[j], j - 1);
66
				add(g[i][j], sum(g, p_a[i], i - 1, p_b[j], j - 1));
67
68
				add(f[i][j], func(func(f[i][j - 1] + f[i - 1][j]) - f[i - 1][j - 1] + mod));
69
				add(g[i][j], func(func(g[i][j - 1] + g[i - 1][j]) - g[i - 1][j - 1] + mod));
70
			}
71
		}
72
		return func(g[n][m]);
73
	}
74
75
	void main() {
76
		scanf("%d %d", &n, &m);
77
		read_arr(a, n), read_arr(b, m);
78
		get_f(f, p_a, a, n), get_f(g, p_b, b, m);
79
		int ans = 0;
80
		for (int i = 1; i <= n; i++) {
81
			for (int j = 1; j < i && j <= m; j++) {
82
				ans = (ans + 1ll * f[n][i] * g[m][j]) % mod;
83
			}
84
		}
85
		add(ans, solve());
86
		printf("%d\n", ans);
87
	}
88
}
89
90
int main() {
91
	__main__::main();
92
	return 0;
93
}

E. Escape

「Gym 102361E」Escape(最大流)

题目描述

给定 $n$ 行 $m$ 列的棋盘,北面某些位置有机器人头朝下,南边有某些位置有出口。棋盘中有一些障碍物,机器人不能通过。你可以在一些没有障碍物的地方放置转弯装置,共有四种(“北 $\leftrightarrow$ 东” 以及其他类似的三种)。问机器人是否能够到达某个出口。

数据范围:$T \le 10, n, m \le 100$。

思路分析

一个重要的观察就是每个转弯装置只能给一个人用。那么考虑网络流,给每个格子拆成两个点:横点和竖点,每排的横点连边,每列的竖点连边,机器人和出口与竖点连边。转弯装置相当于联通横点与竖点,也就是在横点和竖点之间连一条边。这样网络中的所有边都是单位容量边,网络流跑得很快。时间复杂度 $O(T \cdot \text{flow}(n \times m, n \times m))$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
namespace __main__ {
5
	const int maxn = 100, maxv = 3e4, maxe = 2e5, inf = 1e9;
6
	int T, n, m, a[maxn + 3][maxn + 3], A, B;
7
	int cnt, src, snk, row[maxn + 3][maxn + 3], col[maxn + 3][maxn + 3];
8
	int tot, ter[maxe + 3], wei[maxe + 3], nxt[maxe + 3], lnk[maxv + 3];
9
	int dep[maxv + 3], cur[maxv + 3], q[maxv + 3], l, r;
10
11
	void add_e(int u, int v, int w) {
12
		ter[++tot] = v, wei[tot] = w;
13
		nxt[tot] = lnk[u], lnk[u] = tot;
14
	}
15
16
	void add(int u, int v) {
17
		add_e(u, v, 1), add_e(v, u, 0);
18
	}
19
20
	void add_b(int u, int v) {
21
		add(u, v), add(v, u);
22
	}
23
24
	bool bfs() {
25
		memset(dep, -1, sizeof(dep));
26
		dep[src] = 0;
27
		l = r = 0, q[r++] = src;
28
		while (l < r) {
29
			int u = q[l++];
30
			for (int i = lnk[u], v, w; i; i = nxt[i]) {
31
				v = ter[i], w = wei[i];
32
				if (w && dep[v] == -1) {
33
					dep[v] = dep[u] + 1;
34
					q[r++] = v;
35
				}
36
			}
37
		}
38
		return ~dep[snk];
39
	}
40
41
	inline int adj(int x) {
42
		return x & 1 ? x + 1 : x - 1;
43
	}
44
45
	int dfs(int u, int f) {
46
		if (u == snk) {
47
			return f;
48
		}
49
		int x = 0;
50
		for (int &i = cur[u], v, w; i && x < f; i = nxt[i]) {
51
			v = ter[i], w = wei[i];
52
			if (w && dep[v] == dep[u] + 1) {
53
				int t = dfs(v, min(w, f - x));
54
				wei[i] -= t, wei[adj(i)] += t, x += t;
55
			}
56
		}
57
		if (x < f) {
58
			dep[u] = -1;
59
		}
60
		return x;
61
	}
62
63
	int dinic() {
64
		int ans = 0;
65
		while (bfs()) {
66
			memcpy(cur, lnk, sizeof(cur));
67
			ans += dfs(src, inf);
68
		}
69
		return ans;
70
	}
71
72
	void main() {
73
		scanf("%d", &T);
74
		while (T --> 0) {
75
			scanf("%d %d %d %d", &n, &m, &A, &B);
76
			for (int i = 1; i <= n; i++) {
77
				for (int j = 1; j <= m; j++) {
78
					scanf("%1d", &a[i][j]);
79
				}
80
			}
81
			tot = 0;
82
			memset(lnk, 0, sizeof(lnk));
83
			cnt = 2, src = 1, snk = 2;
84
			for (int i = 1; i <= n; i++) {
85
				for (int j = 1; j <= m; j++) {
86
					row[i][j] = ++cnt;
87
				}
88
			}
89
			for (int i = 1; i <= n; i++) {
90
				for (int j = 1; j <= m; j++) {
91
					col[i][j] = ++cnt;
92
				}
93
			}
94
			for (int i = 1, x; i <= A; i++) {
95
				scanf("%d", &x);
96
				add(src, col[1][x]);
97
			}
98
			for (int i = 1, x; i <= B; i++) {
99
				scanf("%d", &x);
100
				add(col[n][x], snk);
101
			}
102
			for (int i = 1; i <= n; i++) {
103
				for (int j = 1; j <= m; j++) {
104
					if (a[i][j] == 0) {
105
						if (i >= 2 && a[i - 1][j] == 0) {
106
							add_b(col[i - 1][j], col[i][j]);
107
						}
108
						if (j >= 2 && a[i][j - 1] == 0) {
109
							add_b(row[i][j - 1], row[i][j]);
110
						}
111
						add_b(row[i][j], col[i][j]);
112
					}
113
				}
114
			}
115
			if (dinic() == A) {
116
				puts("Yes");
117
			} else {
118
				puts("No");
119
			}
120
		}
121
	}
122
}
123
124
int main() {
125
	__main__::main();
126
	return 0;
127
}

H. Houraisan Kaguya

绝对不鸽。

K. MUV LUV UNLIMITED

「Gym 102361K」MUV LUV UNLIMITED(博弈论)

题目描述

有一棵树,先手和后手轮流操作,每次可以拿掉若干叶子结点(不能不拿),拿到根结点的人获胜。问先手是否必胜。

复杂度要求线性。

思路分析

对于某棵树,考虑在它的某个非叶结点下面加上一个叶子结点,那么:

  • 如果原树是必败态,先手第一次只取新叶子后就留下了必败态,那么新树就是必胜态。
  • 如果原树是必胜态,先手第一次取下新叶子和原树必胜态中第一步应该取下的叶子,那么新树还是必胜态。

也就是说,如果一个树满足一个叶子结点的父亲度数大于等于 $2$,它就是必胜态。

如果不是怎么办呢?我们对于每个叶子结点找到第一个祖先 $x$ 满足它的父亲度数大于等于 $2$,我们的目标就是让敌人删到 $x$ 成为叶子结点,然后自己就赢了。这样问题转化成了有 $k$ 个 $a_i$ 表示叶子结点和对应的 $x$ 结点的距离,每次可以选择若干个 $a_i$ 减去 $1$,把其中一个减到 $0$ 的人就输了。

发现只要有一个 $a_i$ 是偶数,你就可以在第一步把所有偶数都取掉一个,之后对手干什么你就复读,最后肯定会在对手的回合留下 $k$ 个 $1$,然后他就输了。否则这时 $a_i$ 全是奇数,对手只要复读你他就能赢。也就是说如果 $a_i$ 全是奇数,你会输;否则你会赢。这样问题就解决了,时间复杂度 $O(n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
const int maxn = 1e6;
5
int T, n, fa[maxn + 3], deg[maxn + 3];
6
7
void print(int x) {
8
	puts(x ? "Takeru" : "Meiya");
9
}
10
11
int main() {
12
	scanf("%d", &T);
13
	while (T --> 0) {
14
		scanf("%d", &n);
15
		for (int i = 1; i <= n; i++) {
16
			deg[i] = 0;
17
		}
18
		for (int i = 2; i <= n; i++) {
19
			scanf("%d", &fa[i]);
20
			deg[fa[i]]++;
21
		}
22
		bool ch = true;
23
		for (int i = 1; i <= n; i++) {
24
			ch &= deg[i] <= 1;
25
		}
26
		if (ch) {
27
			print(n & 1);
28
			continue;
29
		}
30
		bool flag = false;
31
		for (int i = 1; i <= n; i++) if (deg[i] == 0) {
32
			if (deg[fa[i]] >= 2) {
33
				print(1);
34
				flag = true;
35
				break;
36
			}
37
		}
38
		if (flag) {
39
			continue;
40
		}
41
		flag = false;
42
		for (int i = 1; i <= n; i++) if (deg[i] == 0) {
43
			int x = i, c = 0;
44
			while (deg[x] < 2) {
45
				x = fa[x], c++;
46
			}
47
			flag |= c & 1;
48
		}
49
		print(flag);
50
	}
51
	return 0;
52
}

L. MUV LUV ALTERNATIVE

在路上了。