P9170 [省选联考 2023] 填数游戏 题解

Description

众所周知,Alice 和 Bob 是一对好朋友。今天,他们约好一起玩游戏。

一开始,他们各自有一张空白的纸条。接下来,他们会在纸条上依次写 nn[1,m][1,m] 范围内的正整数。等 Alice 写完,Bob 在看到 Alice 写的纸条之后开始写他的纸条

Alice 需要保证她写下的第 ii 个数在集合 SiS_{i} 中,Bob 需要保证他写下的第 ii 个数在集合 TiT_{i} 中。题目保证 1Si,Ti21 \leq\left|S_{i}\right|,\left|T_{i}\right| \leq 2

Alice 喜欢相同,因此,她希望她写下的数与 Bob 写下的数对应位置相同的个数尽量多。Bob 喜欢不同,因此,他希望他写下的 nn 个数 b1,,bnb_{1}, \ldots, b_{n} 互不相同。在此基础上,Bob 希望他写下的数与 Alice 写下的数对应位置相同的个数尽量少。

即设 Alice 写下的数为 a1,,ana_{1}, \ldots, a_{n},Bob 写下的数为 b1,,bnb_{1}, \ldots, b_{n},记 XX 为满足 1in,ai=bi1 \leq i \leq n, a_{i}=b_{i} 的下标 ii 的个数,则

  • Alice 希望最大化 X,X,
  • Bob 在保证 b1,,bnb_{1}, \ldots, b_{n} 互不相同的前提下希望最小化 XX

你首先想知道 Bob 能否保证他写下的 nn 个数互不相同。如果 Bob 能够做到,你想知道在双方均采取最优策略的前提下 XX 的值会是多少。

n,m106,n,m1.5×106n,m\leq 10^6,\sum n,\sum m\leq 1.5\times 10^6

Solution

这题 Bob 在看到 Alice 写的纸条之后才开始写他的纸条!!

先做特殊性质 A。这个就是让你判断 b1,,bnb_1,\dots,b_n 能否互不相同。

考虑把所有 Ti,0T_{i,0}Ti,1T_{i,1} 连边(如果 T=1|T|=1,就连一条 Ti,0T_{i,0} 的自环),题目就转化为将一个图中的边定向,使得每个点的入度不超过 11

容易发现对于每个连通块,如果边数大于点数就一定不合法,否则这个连通块是个树或者基环树,一定合法。


然后考虑怎么对一个基环树求答案。

容易发现基环树除掉环的边的方向是定好的,只有环上的边有两种可能。设环的大小为 kk

所以 Alice 就只要在这 kk 条边对应的 kk 个集合中每个集合选一个数,使得两种方向答案的 min\min 值最大。

首先对于 SiTi=0|S_i\cap T_i|=0 的边,显然没有贡献。SiTi=1|S_i\cap T_i|=1 的边,肯定选择那个交的数,这相当于已经确定了选的数。SiTi=2|S_i\cap T_i|=2 的边,就只能对于两种方向的一种产生贡献。

cuc_u 表示已经确定的数中第一种方向已有的贡献,cuc_u 表示已经确定的数中第二种方向已有的贡献,cc 表示待确定的集合数量。

那么答案就是:

maxi=0c{min{cu+i,cv+ci}}\max_{i=0}^{c}{\left\{\min\{c_u+i,c_v+c-i\}\right\}}

这个暴力求就行了。

这一部分的时间复杂度:O(n)O(n)


然后考虑树的情况。

显然定向方式就是选定一个点作为根的外向树。

对于交为 11 的边 uvu\to v,如果 uu 是交的数,那么根一定在 vv 的子树里,否则根一定在 vv 的子树外。

这一部分的贡献可以用 dfs 序上差分维护。

fif_i 表示以 ii 为根的答案。

然后题目转化为对于所有交为 22 的边 uvu\to v,要选择把 vv 子树内或子树外的 ff 全加一,使得最后 min{f1,,fn}\min\{f_1,\dots,f_n\} 最大。

观察可知如果两个点他们要将 ff 加一的集合不交,则把这两个集合都换成原来的补集一定更优。

所以对于两个点 u,vu,v,如果 uuvv 的祖先,那么不能出现 uu 选子树外和 vv 选子树内的情况。如果 uu 不是 vv 祖先,那么不能出现 uuvv 都选子树外。

于是根据这两个性质可以得到选子树的点一定构成一个到根的链,然后 dfs 一边用线段树维护答案即可。

这一部分的时间复杂度:O(nlogn)O(n\log n)

所以总的时间复杂度就是 O(nlogn)O(n\log n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#include <bits/stdc++.h>

// #define int int64_t

namespace FASTIO {
char ibuf[1 << 21], *p1 = ibuf, *p2 = ibuf;
char getc() {
return p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
template<class T> bool read(T &x) {
x = 0; int f = 0; char ch = getc();
while (ch < '0' || ch > '9') f |= ch == '-', ch = getc();
while (ch >= '0' && ch <= '9') x = (x * 10) + (ch ^ 48), ch = getc();
x = (f ? -x : x); return 1;
}
template<typename A, typename ...B> bool read(A &x, B &...y) { return read(x) && read(y...); }

char obuf[1 << 21], *o1 = obuf, *o2 = obuf + (1 << 21) - 1;
void flush() { fwrite(obuf, 1, o1 - obuf, stdout), o1 = obuf; }
void putc(char x) { *o1++ = x; if (o1 == o2) flush(); }
template<class T> void write(T x) {
if (!x) putc('0');
if (x < 0) x = -x, putc('-');
char c[40]; int tot = 0;
while (x) c[++tot] = x % 10, x /= 10;
for (int i = tot; i; --i) putc(c[i] + '0');
}
void write(char x) { putc(x); }
void write(char *x) { while (*x) putc(*x++); }
void write(const char *x) { while (*x) putc(*x++); }
template<typename A, typename ...B> void write(A x, B ...y) { write(x), write(y...); }
struct Flusher {
~Flusher() { flush(); }
} flusher;
} // namespace FASTIO
using FASTIO::read; using FASTIO::putc; using FASTIO::write;

const int kMaxN = 1e6 + 5;

int n, m;
int cnt[kMaxN], fa[kMaxN], sz[kMaxN], cnted[kMaxN];
std::vector<int> s[kMaxN], t[kMaxN];
std::vector<std::pair<int, int>> G[kMaxN];

int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void unionn(int x, int y) {
int fx = find(x), fy = find(y);
if (fx != fy) {
fa[fx] = fy, sz[fy] += sz[fx], cnted[fy] += cnted[fx];
}
++cnted[fy];
}

void addedge(int u, int v, int id) {
G[u].emplace_back(v, id), unionn(u, v);
if (u != v) G[v].emplace_back(u, id);
}

namespace Cycle {
int res, p[kMaxN], pid[kMaxN], dep[kMaxN];
bool vis[kMaxN], onc[kMaxN];
std::vector<int> circ, circ_v;

void clear() {
std::fill_n(vis + 1, m, 0);
std::fill_n(onc + 1, m, 0);
std::fill_n(p + 1, m, 0);
}

void dfs1(int u, int fa, int faid) {
p[u] = fa, dep[u] = dep[fa] + 1, vis[u] = 1;
for (auto [v, id] : G[u]) {
if (id == faid) continue;
if (!vis[v]) {
pid[v] = id, dfs1(v, u, id);
} else if (dep[u] >= dep[v]) {
for (int i = u; i != v; i = p[i]) {
circ.emplace_back(pid[i]);
circ_v.emplace_back(i);
onc[i] = 1;
}
circ.emplace_back(id), onc[v] = 1, circ_v.emplace_back(v);
}
}
}

void dfs2(int u, int fa) {
for (auto [v, id] : G[u]) {
if (v == fa || onc[v]) continue;
res += (v == s[id][0] || v == s[id].back());
dfs2(v, u);
}
}

int solve(int rt) {
res = 0, circ.clear(), circ_v.clear(), dfs1(rt, 0, 0);
for (auto x : circ_v) dfs2(x, 0);
assert(circ.size() == circ_v.size());
int cu = 0, cv = 0, c = 0;
for (int i = 0; i < circ.size(); ++i) {
if (cnt[circ[i]] == 1) {
if (circ_v[i] == s[circ[i]][0] || circ_v[i] == s[circ[i]].back()) ++cu;
else ++cv;
} else if (cnt[circ[i]] == 2) {
++c;
}
}
if (circ.size() == 1) c *= 2;
return res + (abs(cu - cv) <= c ? (cu + cv + c) / 2 : std::min(cu, cv) + c);
}
} // namespace Cycle

namespace Tree {
struct SGT {
int mi[kMaxN * 4], tag[kMaxN * 4];

void pushup(int x) {
mi[x] = std::min(mi[x << 1], mi[x << 1 | 1]);
}

void addtag(int x, int v) {
mi[x] += v, tag[x] += v;
}

void pushdown(int x) {
if (!tag[x]) return;
addtag(x << 1, tag[x]), addtag(x << 1 | 1, tag[x]);
tag[x] = 0;
}

void build(int x, int l, int r) {
mi[x] = tag[x] = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
}

void update(int x, int l, int r, int ql, int qr, int v) {
if (l > qr || r < ql) {
return;
} else if (l >= ql && r <= qr) {
return addtag(x, v);
}
pushdown(x);
int mid = (l + r) >> 1;
update(x << 1, l, mid, ql, qr, v), update(x << 1 | 1, mid + 1, r, ql, qr, v);
pushup(x);
}
} sgt;

int res, dfnc;
int dfn[kMaxN], sz[kMaxN];

void dfs1(int u, int fa) {
dfn[u] = ++dfnc, sz[u] = 1;
for (auto [v, id] : G[u]) {
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
}
}

void dfs2(int u, int fa) {
for (auto [v, id] : G[u]) {
if (v == fa) continue;
if (cnt[id] == 1) {
if (v == s[id][0] || v == s[id].back()) {
sgt.update(1, 1, dfnc, 1, dfn[v] - 1, 1), sgt.update(1, 1, dfnc, dfn[v] + sz[v], dfnc, 1);
} else {
sgt.update(1, 1, dfnc, dfn[v], dfn[v] + sz[v] - 1, 1);
}
} else if (cnt[id] == 2) {
sgt.update(1, 1, dfnc, 1, dfn[v] - 1, 1), sgt.update(1, 1, dfnc, dfn[v] + sz[v], dfnc, 1);
}
dfs2(v, u);
}
}

void dfs3(int u, int fa) {
res = std::max(res, sgt.mi[1]);
for (auto [v, id] : G[u]) {
if (v == fa) continue;
if (cnt[id] == 2) {
sgt.update(1, 1, dfnc, 1, dfn[v] - 1, -1);
sgt.update(1, 1, dfnc, dfn[v] + sz[v], dfnc, -1);
sgt.update(1, 1, dfnc, dfn[v], dfn[v] + sz[v] - 1, 1);
}
dfs3(v, u);
if (cnt[id] == 2) {
sgt.update(1, 1, dfnc, 1, dfn[v] - 1, 1);
sgt.update(1, 1, dfnc, dfn[v] + sz[v], dfnc, 1);
sgt.update(1, 1, dfnc, dfn[v], dfn[v] + sz[v] - 1, -1);
}
}
}

int solve(int rt) {
res = dfnc = 0;
dfs1(rt, 0);
sgt.build(1, 1, dfnc);
dfs2(rt, 0), dfs3(rt, 0);
return res;
}
} // namespace Tree

void dickdreamer() {
read(n, m);
for (int i = 1; i <= m; ++i) {
G[i].clear();
fa[i] = i, sz[i] = 1, cnted[i] = 0;
}
Cycle::clear();
for (int i = 1; i <= n; ++i) {
int c;
read(c);
s[i].clear();
for (int j = 1; j <= c; ++j) {
int x;
read(x);
s[i].emplace_back(x);
}
}
for (int i = 1; i <= n; ++i) {
int c;
read(c);
cnt[i] = 0, t[i].clear();
for (int j = 1; j <= c; ++j) {
int x;
read(x);
t[i].emplace_back(x);
for (auto y : s[i]) cnt[i] += (x == y);
}
if (c == 1) cnt[i] *= 2;
if (c == 1) addedge(t[i][0], t[i][0], i);
else addedge(t[i][0], t[i][1], i);
}
int ans = 0;
for (int i = 1; i <= m; ++i) {
if (i == find(i)) {
if (cnted[i] > sz[i]) return void(write("-1\n"));
else if (cnted[i] == sz[i]) ans += Cycle::solve(i);
else ans += Tree::solve(i);
}
}
write(ans, '\n');
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T = 1;
read(T);
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P9170 [省选联考 2023] 填数游戏 题解
https://sobaliuziao.github.io/2024/02/09/post/80c9f83.html
作者
Egg_laying_master
发布于
2024年2月9日
许可协议