简介
本文包括 IOI 2020 集训队作业 31 ~ 35 题的题解,下面是题单:
- 31.「AGC 030D」Inversion Sum
- 32.「AGC 030E」Less than 3
- 33.「AGC 030F」Permutation and Minimum
- 34.「AGC 031D」A Sequence of Permutations
- 35.「AGC 031E」Snuke the Phantom Thief
- 36.「AGC 031F」Walk on Graph
Inversion Sum
$f(i, j)$ 表示 $a_i > a_j$ 的概率,直接转移即可,时间复杂度 $O(n (n + q))$。
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 3000, mod = 1e9 + 7, inv2 = (mod + 1) >> 1; |
5 | int n, q, a[maxn + 3], f[maxn + 3][maxn + 3], g[maxn + 3][maxn + 3]; |
6 | |
7 | int main() { |
8 | scanf("%d %d", &n, &q); |
9 | for (int i = 1; i <= n; i++) { |
10 | scanf("%d", &a[i]); |
11 | } |
12 | for (int i = 1; i < n; i++) { |
13 | for (int j = i + 1; j <= n; j++) { |
14 | f[i][j] = a[i] > a[j]; |
15 | f[j][i] = a[j] > a[i]; |
16 | } |
17 | } |
18 | int tot = 1; |
19 | for (int x, y; q --> 0; ) { |
20 | tot = 2ll * tot % mod; |
21 | scanf("%d %d", &x, &y); |
22 | for (int i = 1; i <= n; i++) { |
23 | if (i != x) g[i][x] = f[i][x], g[x][i] = f[x][i]; |
24 | if (i != y) g[i][y] = f[i][y], g[y][i] = f[y][i]; |
25 | } |
26 | f[x][y] = 1ll * (g[x][y] + g[y][x]) * inv2 % mod; |
27 | f[y][x] = 1ll * (g[x][y] + g[y][x]) * inv2 % mod; |
28 | for (int i = 1; i <= n; i++) if (i != x && i != y) { |
29 | f[i][x] = 1ll * (g[i][x] + g[i][y]) * inv2 % mod; |
30 | f[i][y] = 1ll * (g[i][x] + g[i][y]) * inv2 % mod; |
31 | f[x][i] = 1ll * (g[x][i] + g[y][i]) * inv2 % mod; |
32 | f[y][i] = 1ll * (g[x][i] + g[y][i]) * inv2 % mod; |
33 | } |
34 | } |
35 | int sum = 0; |
36 | for (int i = 1; i < n; i++) { |
37 | for (int j = i + 1; j <= n; j++) { |
38 | sum = (sum + f[i][j]) % mod; |
39 | } |
40 | } |
41 | printf("%lld\n", 1ll * sum * tot % mod); |
42 | return 0; |
43 | } |
Less than 3
在每个相邻的 01 之间插红色隔板,10 之间插蓝色隔板,左右两边补上无限个隔板。这样给一个数取反相当于将一个隔板移动一格,那么我们枚举隔板的一个匹配,显然移动步数的下界是隔板距离之和。发现下界可以取到,所以直接计算取最小值即可,时间复杂度 $O(n^2)$。
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 1e4, inf = 1e9; |
5 | int n, k, l, a[maxn + 3], b[maxn + 3]; |
6 | char s[maxn + 3], t[maxn + 3]; |
7 | |
8 | int _abs(int x) { |
9 | return x < 0 ? -x : x; |
10 | } |
11 | |
12 | int solve(int p, int q) { |
13 | int ret = 0; |
14 | for (int i = 0; i + p <= k || i + q <= l; i++) { |
15 | int x = i + p <= k ? a[i + p] : n; |
16 | int y = i + q <= l ? b[i + q] : n; |
17 | ret += _abs(x - y); |
18 | } |
19 | for (int i = 1; p - i >= 1 || q - i >= 1; i++) { |
20 | int x = p - i >= 1 ? a[p - i] : 0; |
21 | int y = q - i >= 1 ? b[q - i] : 0; |
22 | ret += _abs(x - y); |
23 | } |
24 | return ret; |
25 | } |
26 | |
27 | int main() { |
28 | scanf("%d %s %s", &n, s + 1, t + 1); |
29 | for (int i = 1; i < n; i++) { |
30 | if (s[i] != s[i + 1]) { |
31 | a[++k] = i; |
32 | } |
33 | } |
34 | for (int i = 0; i < 3; i++) { |
35 | a[++k] = 0, a[++k] = n; |
36 | } |
37 | for (int i = 1; i < n; i++) { |
38 | if (t[i] != t[i + 1]) { |
39 | b[++l] = i; |
40 | } |
41 | } |
42 | for (int i = 0; i < 3; i++) { |
43 | b[++l] = 0, b[++l] = n; |
44 | } |
45 | if (t[1] != s[1]) { |
46 | b[++l] = 0; |
47 | } |
48 | sort(a + 1, a + k + 1); |
49 | sort(b + 1, b + l + 1); |
50 | int ans = inf; |
51 | for (int i = 1; i <= k; i += 2) { |
52 | ans = min(ans, solve(i, 1)); |
53 | } |
54 | for (int i = 1; i <= l; i += 2) { |
55 | ans = min(ans, solve(1, i)); |
56 | } |
57 | printf("%d\n", ans); |
58 | return 0; |
59 | } |
Permutation and Minimum
首先我们将每个不是 $-1$ 的 $p_i$ 变成 $2n - p_i + 1$ 方便处理。$(p_{2i - 1}, p_{2i})$ 可以分成三种情况:$(x, y), (x, -1), (-1, -1)$。$(x, y)$ 可以直接扔掉,我们先考虑不存在 $(x, -1)$ 的情况。可以看作是有 $2n$ 个点,将它们两两配对后右端点取值形成的集合的个数。考虑前 $i$ 个数,$i$ 可以匹配之前的某一个没选过的端点,或者自己暂时不选。发现方案数就是卡特兰数。当然对于一种匹配的方案,有 $n$ 对点,我们要把它分配到 $n$ 个位置,答案要额外地乘上 $n!$。
接下来考虑 $(x, -1)$,一个 $(x, -1)$ 就表示 $x$ 号点要匹配一个空的点,我们称 $x$ 为特殊点。记 $f(i, j, k)$ 表示考虑前 $i$ 个位置,有 $j$ 个未匹配的普通点,有 $k$ 个未匹配的特殊点。那么如果第 $i$ 个位置是特殊点,它要么暂时不选,要么匹配普通点。如果第 $i$ 个位置是普通点,它要么暂时不选,要么匹配普通点,要么匹配特殊点。注意匹配不同的特殊点时在原序列中的位置是不同的,所以方案数要乘上特殊点个数。那么最后的方案数就是 $f(n, 0, 0) \times c!$,$c$ 表示 $(-1, -1)$ 的对数。直接 dp,时间复杂度 $O(n^3)$。
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 300, maxm = maxn * 2, mod = 1e9 + 7; |
5 | int n, m, a[maxm + 3], cnt[maxm + 3], f[maxm + 3][maxn + 3][maxn + 3]; |
6 | bool tag[maxm + 3]; |
7 | |
8 | void upd(int &x, int y) { |
9 | x += y, x < mod ? 0 : x -= mod; |
10 | } |
11 | |
12 | int main() { |
13 | scanf("%d", &n); |
14 | m = n * 2; |
15 | for (int i = 1; i <= m; i++) { |
16 | scanf("%d", &a[i]); |
17 | if (a[i] != -1) { |
18 | a[i] = m - a[i] + 1; |
19 | } |
20 | cnt[i] = 1; |
21 | } |
22 | for (int i = 1; i <= m; i += 2) { |
23 | if (a[i] != -1 && a[i + 1] != -1) { |
24 | cnt[a[i]] = 0, cnt[a[i + 1]] = 0; |
25 | } |
26 | } |
27 | for (int i = 2; i <= m; i++) { |
28 | cnt[i] += cnt[i - 1]; |
29 | } |
30 | int num = 1, cur = 0; |
31 | for (int i = 1; i <= m; i += 2) { |
32 | if (a[i] == -1 && a[i + 1] == -1) { |
33 | num = 1ll * num * ++cur % mod; |
34 | } else if (a[i] == -1 || a[i + 1] == -1) { |
35 | tag[cnt[a[i] + a[i + 1] + 1]] = true; |
36 | } |
37 | } |
38 | m = cnt[m], n = m / 2; |
39 | f[0][0][0] = 1; |
40 | for (int i = 1; i <= m; i++) { |
41 | for (int j = 0; j <= n; j++) { |
42 | for (int k = 0; k <= n; k++) { |
43 | if (tag[i]) { |
44 | upd(f[i][j][k], f[i - 1][j + 1][k]); |
45 | if (k > 0) { |
46 | upd(f[i][j][k], f[i - 1][j][k - 1]); |
47 | } |
48 | } else { |
49 | upd(f[i][j][k], f[i - 1][j + 1][k]); |
50 | upd(f[i][j][k], 1ll * f[i - 1][j][k + 1] * (k + 1) % mod); |
51 | if (j > 0) { |
52 | upd(f[i][j][k], f[i - 1][j - 1][k]); |
53 | } |
54 | } |
55 | } |
56 | } |
57 | } |
58 | printf("%lld\n", 1ll * f[m][0][0] * num % mod); |
59 | return 0; |
60 | } |
A Sequence of Permutations
对于排列 $p, q$,定义 $pq$ 表示第 $i$ 位为 $p_{q_i}$ 的排列,$p^{-1}$ 表示第 $p_i$ 位为 $i$ 的排列,那么我们有 $(pq)^{-1} = q^{-1}p^{-1}$,$p^m p^n = p^{m + n}$,$(pq)r = p(qr)$,并且题目里的 $f(p, q) = qp^{-1}$。记 $g(p, q) = pqp^{-1}$,那么有:
也就是 $g(r,p)$ 和 $g(r,q)$ 在 $6$ 次之后会变成 $g(rqp^{-1}q^{-1}p,p)$ 和 $g(rqp^{-1}q^{-1}p,q)$,所以只要求出 $(qp^{-1}q^{-1}p)^{\lfloor \frac{k - 1}{6} \rfloor}$ 即可,时间复杂度 $O(n \log k)$ 或 $O(n)$。
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 1e5; |
5 | int n, k; |
6 | |
7 | struct perm { |
8 | int n, a[maxn + 3]; |
9 | |
10 | perm(int m) { |
11 | n = m; |
12 | for (int i = 1; i <= n; i++) { |
13 | a[i] = i; |
14 | } |
15 | } |
16 | |
17 | friend perm operator * (perm p, perm q) { |
18 | perm r(p.n); |
19 | for (int i = 1; i <= p.n; i++) { |
20 | r.a[i] = p.a[q.a[i]]; |
21 | } |
22 | return r; |
23 | } |
24 | |
25 | perm inv() { |
26 | perm r(n); |
27 | for (int i = 1; i <= n; i++) { |
28 | r.a[a[i]] = i; |
29 | } |
30 | return r; |
31 | } |
32 | |
33 | void print() { |
34 | for (int i = 1; i <= n; i++) { |
35 | printf("%d%c", a[i], " \n"[i == n]); |
36 | } |
37 | } |
38 | }; |
39 | |
40 | perm g(perm p, perm q) { |
41 | return p * q * p.inv(); |
42 | } |
43 | |
44 | perm qpow(perm p, int k) { |
45 | perm q(n); |
46 | for (; k; k >>= 1, p = p * p) { |
47 | if (k & 1) q = p * q; |
48 | } |
49 | return q; |
50 | } |
51 | |
52 | int main() { |
53 | scanf("%d %d", &n, &k); |
54 | perm p(n), q(n), r(n); |
55 | for (int i = 1; i <= n; i++) { |
56 | scanf("%d", &p.a[i]); |
57 | } |
58 | for (int i = 1; i <= n; i++) { |
59 | scanf("%d", &q.a[i]); |
60 | } |
61 | int x = (k - 1) / 6; |
62 | r = q * p.inv() * q.inv() * p; |
63 | r = qpow(r, x); |
64 | k -= x * 6; |
65 | if (k == 1) { |
66 | g(r, p).print(); |
67 | } else if (k == 2) { |
68 | g(r, q).print(); |
69 | } else if (k == 3) { |
70 | g(r, q * p.inv()).print(); |
71 | } else if (k == 4) { |
72 | g(r, q * p.inv() * q.inv()).print(); |
73 | } else if (k == 5) { |
74 | g(r, q * p.inv() * q.inv() * p * q.inv()).print(); |
75 | } else { |
76 | g(r, q * p.inv() * q.inv() * p * p * q.inv()).print(); |
77 | } |
78 | return 0; |
79 | } |
Snuke the Phantom Thief
首先枚举选的总个数 $T$,对于一种选的方案记 x 坐标从小到大为 $x_1, x_2, \cdots, x_T$。那么 L
$a_i, b_i$ 的限制相当于强制 $x_{b_i + 1} \ge a_i + 1$,R
$a_i, b_i$ 的限制相当于强制 $x_{T - b_i} \le a_i - 1$。我们可以给每个 $x_i$ 加一个限制 $l_i \le x_i \le r_i$,并满足 $l_i \le l_{i + 1}, r_i \le r_{i + 1}$。D
, U
的限制同理,我们搞出 $d_i \le y_i \le u_i$。然后我们建图:
- 每个珠宝拆成 A 和 B,A 向 B 连边,容量为 $1$,费用为 $-v_i$。
- 源点连向每个 x 限制,容量为 $1$,费用为 $0$。
- 每个 x 限制向可行的珠宝 A 连边,容量为 $1$,费用为 $0$。
- 每个珠宝 B 向可行的 y 限制连边,容量为 $1$,费用为 $0$。
- 每个 y 限制连向汇点,容量为 $1$,费用为 $0$。
思考一下发现这样是对的,因为如果第 $i$ 个 x 限制选择的 x 坐标大于第 $i + 1$ 个 x 限制选择的 x 坐标的话一定是不优的。于是我们枚举 $T$,给每个费用预先加上 $10^{15}$,跑最小费用最大流,最后将答案减去 $10^{15}T$ 并取最小值即可。因为最大流的大小是 $O(n)$ 的,图的规模是 $O(n) - O(n^2)$ 的,我们还要跑 $n$ 遍这样的算法,所以时间复杂度为 $O(n^4)$。
1 |
|
2 | using namespace std; |
3 | |
4 | typedef long long ll; |
5 | const int maxn = 320, maxm = 1e5; |
6 | const ll lim = 1e15, inf = 1e18; |
7 | int n, x[maxn + 3], y[maxn + 3], m, a[maxn + 3], b[maxn + 3]; |
8 | int l[maxn + 3], r[maxn + 3], u[maxn + 3], d[maxn + 3]; |
9 | int tot, wei[maxm + 3], ter[maxm + 3], nxt[maxm + 3], lnk[maxn + 3]; |
10 | int src, snk, pre[maxn + 3]; |
11 | bool in[maxn + 3]; |
12 | ll v[maxn + 3], len[maxm + 3], dist[maxm + 3]; |
13 | char s[maxn + 3]; |
14 | |
15 | int func(int x) { |
16 | return x & 1 ? x + 1 : x - 1; |
17 | } |
18 | |
19 | void add(int u, int v, int w, ll l) { |
20 | ter[++tot] = v, wei[tot] = w, len[tot] = l; |
21 | nxt[tot] = lnk[u], lnk[u] = tot; |
22 | } |
23 | |
24 | void add_f(int u, int v, int w, ll l) { |
25 | add(u, v, w, l); |
26 | add(v, u, 0, -l); |
27 | } |
28 | |
29 | bool spfa() { |
30 | queue<int> Q; |
31 | Q.push(src), in[src] = true; |
32 | fill(dist + 1, dist + snk + 1, inf); |
33 | dist[src] = 0; |
34 | while (!Q.empty()) { |
35 | int u = Q.front(); |
36 | Q.pop(); |
37 | in[u] = false; |
38 | ll l = 0; |
39 | for (int i = lnk[u], v; i; i = nxt[i]) { |
40 | v = ter[i], l = len[i]; |
41 | if (wei[i] && dist[u] + l < dist[v]) { |
42 | dist[v] = dist[u] + l; |
43 | pre[v] = i; |
44 | if (!in[v]) { |
45 | in[v] = true; |
46 | Q.push(v); |
47 | } |
48 | } |
49 | } |
50 | } |
51 | return dist[snk] != inf; |
52 | } |
53 | |
54 | void flow(int &res, ll &ans) { |
55 | while (spfa()) { |
56 | int x = snk; |
57 | while (x != src) { |
58 | wei[pre[x]]--, wei[func(pre[x])]++; |
59 | x = ter[func(pre[x])]; |
60 | } |
61 | res++; |
62 | ans += dist[snk]; |
63 | } |
64 | } |
65 | |
66 | int main() { |
67 | scanf("%d", &n); |
68 | for (int i = 1; i <= n; i++) { |
69 | scanf("%d %d %lld", &x[i], &y[i], &v[i]); |
70 | } |
71 | scanf("%d", &m); |
72 | for (int i = 1; i <= m; i++) { |
73 | char t[5]; |
74 | scanf("%s %d %d", t + 1, &a[i], &b[i]); |
75 | s[i] = t[1]; |
76 | if (s[i] == 'U') { |
77 | s[i] = 'D'; |
78 | } else if (s[i] == 'D') { |
79 | s[i] = 'U'; |
80 | } |
81 | } |
82 | ll ans = 0; |
83 | for (int T = 1; T <= n; T++) { |
84 | fill(l + 1, l + T + 1, 1); |
85 | fill(r + 1, r + T + 1, 100); |
86 | fill(u + 1, u + T + 1, 1); |
87 | fill(d + 1, d + T + 1, 100); |
88 | for (int i = 1; i <= m; i++) { |
89 | if (b[i] + 1 > T) { |
90 | continue; |
91 | } |
92 | if (s[i] == 'L') { |
93 | l[b[i] + 1] = max(l[b[i] + 1], a[i] + 1); |
94 | } else if (s[i] == 'R') { |
95 | r[T - b[i]] = min(r[T - b[i]], a[i] - 1); |
96 | } else if (s[i] == 'U') { |
97 | u[b[i] + 1] = max(u[b[i] + 1], a[i] + 1); |
98 | } else if (s[i] == 'D') { |
99 | d[T - b[i]] = min(d[T - b[i]], a[i] - 1); |
100 | } |
101 | } |
102 | for (int i = 2; i <= T; i++) { |
103 | l[i] = max(l[i], l[i - 1]); |
104 | u[i] = max(u[i], u[i - 1]); |
105 | } |
106 | for (int i = T - 1; i; i--) { |
107 | r[i] = min(r[i], r[i + 1]); |
108 | d[i] = min(d[i], d[i + 1]); |
109 | } |
110 | src = n * 4 + 1, snk = src + 1; |
111 | tot = 0; |
112 | fill(lnk + 1, lnk + snk + 1, 0); |
113 | for (int i = 1; i <= T; i++) { |
114 | add_f(src, i, 1, 0); |
115 | for (int j = 1; j <= n; j++) { |
116 | if (x[j] >= l[i] && x[j] <= r[i]) { |
117 | add_f(i, j + n, 1, 0); |
118 | } |
119 | } |
120 | } |
121 | for (int i = 1; i <= n; i++) { |
122 | add_f(i + n, i + n * 2, 1, lim - v[i]); |
123 | } |
124 | for (int i = 1; i <= T; i++) { |
125 | add_f(i + n * 3, snk, 1, 0); |
126 | for (int j = 1; j <= n; j++) { |
127 | if (y[j] >= u[i] && y[j] <= d[i]) { |
128 | add_f(j + n * 2, i + n * 3, 1, 0); |
129 | } |
130 | } |
131 | } |
132 | int fl = 0; |
133 | ll mx = 0; |
134 | flow(fl, mx); |
135 | if (fl == T && T * lim - mx > ans) { |
136 | ans = T * lim - mx; |
137 | } |
138 | } |
139 | printf("%lld\n", ans); |
140 | return 0; |
141 | } |