「解题报告」P9170 [省选联考 2023] 填数游戏
考场上读错题了,而且也没想到树的情况怎么做。考场上的思路也很乱,感觉根本无从下手。
感觉心态还是决定很多东西的。今天写这个题一步一步写出来,自我感觉还满清楚的。
这种题从特殊性质入手。
首先特殊性质 A 让我们判定 Bob 是否有解。
由于集合大小为 \(2\)(\(1\) 可以复制一遍变为 \(2\)),我们可以将它建出图来,每个集合看作一条边。那么 Bob 选择互不相同的限制其实就是在图上,每个边选择两个端点之一,且不能选择重复的端点。这个是很容易的,因为对于一个连通块来说,要不然有 \(n - 1\) 条边,要不然有 \(n\) 条边,如果边再多显然不合法,因为点的个数不够选的。而容易发现,树与基环树的情况一定存在合法方案。那么我们只需要拿并查集维护一下,看看最后得到的图中是否都是树或基环树即可。
再考虑特殊性质 B,发现是一个环。这个环对应着基环树的情况。容易发现,在这个环上,Bob 只有两种选择方案,要不然都选左边的点,要不然都选右边的点。那么对于每一种选择方案,我们可以计算出 Alice 选哪些点能够使得答案加 1。可以发现,分为了三种情况,一种是 Alice 可以在两个端点中任选一个,这时候可以对两种方案之一造成贡献,第二种情况是 Alice 只能选两个端点中的一个,只能对一种方案造成贡献,第三种情况就是都选不了,都造成不了贡献。那么假如设 \(a, b, c\) 分别表示只能对第一种选择方案造成的贡献,只能对第二种方案造成的贡献与两者均可造成的贡献,那么我们就是要最大化 \(\min_{0 \le i \le c}(a + i, b + c - i)\),容易分情况讨论得到答案为 \(\begin{cases}\lfloor\frac{a + b + c}{2}\rfloor&|a-b|\le c\\\min(a, b)+c&|a - b|>c\end{cases}\)。注意特判一下环是一个自环的情况。
对于基环树来说,发现环上的选择方案都是确定了的,那么向外连的树的方案其实是唯一固定的,这时候 Alice 容易得出贡献最大的选择方案。
下面考虑树的情况。首先发现,Bob 有 \(n\) 种选择方案,即选择一个点不选,其他的都往外选。但是 Alice 有 \(2^n\) 种选择方案,还是很难考虑。考虑对于 Bob 的每一种选择方案都维护出一个答案,那么对于 Alice 来说,如果 \(u-v\) 的一条边中选择 \(u\),那么就会对 \(v\) 子树中的所有点造成 \(1\) 的贡献。
我们随便取一个点为根,那么每一个点的贡献相当于是给子树内或子树外的所有点进行贡献。我们可以通过分析一些情况来减少 Alice 的选择方案:
- 假如存在两个点 \(u, v\) 不存在祖先关系,且满足 \(u\) 向子树内贡献,\(v\) 也向子树内贡献,那么显然不如 \(u\) 向子树外贡献,\(v\) 向子树外贡献更优;
- 假如 \(u\) 是 \(v\) 的祖先,且满足 \(u\) 向子树外贡献,\(v\) 向子树内贡献,那么显然不如 \(u\) 向子树内贡献,\(v\) 向子树外贡献。
综合上述两种情况,我们可以得出结论:Alice 只会将根到某个点的所有边选择向子树内贡献,其它的点都会选择向子树外贡献(这等价于存在一个点 \(u\),使得所有的边都会向 \(u\) 方向贡献)。这意味着 Alice 只有 \(n\) 种方案。
我们只需要对这 \(n\) 种选择方案快速维护出 Bob 的答案即可,可以 DFS 一遍,然后每次对子树内或子树外加减 1 即可维护出答案。我直接拿线段树维护的。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
int T;
int n, m, su[MAXN], sv[MAXN], tu[MAXN], tv[MAXN];
int fa[MAXN], ce[MAXN];
bool cyl[MAXN];
vector<pair<int, int>> e[MAXN];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
bool merge(int u, int v, int i) {
int ru = find(u), rv = find(v);
if (ru == rv) {
if (ce[ru]) return false;
ce[ru] = i;
} else {
if (ce[ru] && ce[rv]) return false;
fa[ru] = rv, ce[rv] |= ce[ru];
e[u].push_back({ v, i }), e[v].push_back({ u, i });
}
return true;
}
int f[MAXN], dep[MAXN], dfn[MAXN], ed[MAXN], dcnt;
int fe[MAXN];
void dfs(int u, int pre) {
f[u] = pre, dep[u] = dep[pre] + 1;
dfn[u] = ++dcnt;
for (auto [v, i] : e[u]) if (v != pre) {
dfs(v, u);
fe[v] = i;
}
ed[u] = dcnt;
}
pair<vector<int>, vector<int>> findCycle(int u, int v) {
vector<int> a, b;
while (dep[u] > dep[v]) cyl[u] = 1, a.push_back(u), u = f[u];
while (dep[u] < dep[v]) cyl[v] = 1, b.push_back(v), v = f[v];
while (u != v) cyl[u] = 1, a.push_back(u), u = f[u], cyl[v] = 1, b.push_back(v), v = f[v];
reverse(b.begin(), b.end());
vector<int> ed;
for (int i : a) ed.push_back(fe[i]);
for (int i : b) ed.push_back(fe[i]);
cyl[u] = 1, a.push_back(u);
for (int i : b) a.push_back(i);
return { a, ed };
}
int solveCycleTree(int u, int pre, int i = 0) {
int ans = 0;
if (pre) {
if (su[i] == u || sv[i] == u) ans++;
}
for (auto [v, i] : e[u]) if (v != pre && !cyl[v]) {
ans += solveCycleTree(v, u, i);
}
return ans;
}
int cid[MAXN], cu[MAXN], cv[MAXN];
struct SegmentTree {
struct Node {
int mn, tag;
} t[MAXN << 2];
#define lc (i << 1)
#define rc (i << 1 | 1)
void build(int i = 1, int l = 1, int r = dcnt) {
t[i].mn = t[i].tag = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
}
inline void tag(int i, int v) { t[i].mn += v, t[i].tag += v; }
inline void pushDown(int i) { if (t[i].tag) tag(lc, t[i].tag), tag(rc, t[i].tag), t[i].tag = 0; }
void add(int a, int b, int v, int i = 1, int l = 1, int r = dcnt) {
if (a <= l && r <= b) return tag(i, v);
int mid = (l + r) >> 1;
pushDown(i);
if (a <= mid) add(a, b, v, lc, l, mid);
if (b > mid) add(a, b, v, rc, mid + 1, r);
t[i].mn = min(t[lc].mn, t[rc].mn);
}
} st;
void precompute(int u, int pre, int i = 0) {
if (pre) {
if ((u == su[i] && pre == sv[i]) || (u == sv[i] && pre == su[i])) {
st.add(1, dcnt, 1), st.add(dfn[u], ed[u], -1);
} else if (u == su[i] || u == sv[i]) {
st.add(1, dcnt, 1), st.add(dfn[u], ed[u], -1);
} else if (pre == su[i] || pre == sv[i]) {
st.add(dfn[u], ed[u], 1);
}
}
for (auto [v, i] : e[u]) if (v != pre) {
precompute(v, u, i);
}
}
int fans;
void solveTree(int u, int pre, int i = 0) {
if (pre) {
if ((u == su[i] && pre == sv[i]) || (u == sv[i] && pre == su[i])) {
st.add(1, dcnt, -1), st.add(dfn[u], ed[u], 2);
}
}
fans = max(fans, st.t[1].mn);
for (auto [v, i] : e[u]) if (v != pre) {
solveTree(v, u, i);
}
if (pre) {
if ((u == su[i] && pre == sv[i]) || (u == sv[i] && pre == su[i])) {
st.add(1, dcnt, 1), st.add(dfn[u], ed[u], -2);
}
}
}
int main() {
// freopen("game9.in", "r", stdin);
// freopen("game9.out", "w", stdout);
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
int c; scanf("%d%d", &c, &su[i]);
if (c == 1) sv[i] = su[i];
else scanf("%d", &sv[i]);
}
for (int i = 1; i <= n; i++) {
int c; scanf("%d%d", &c, &tu[i]);
if (c == 1) tv[i] = tu[i];
else scanf("%d", &tv[i]);
}
// MULTITASK CLEARING START
for (int i = 1; i <= m; i++)
fa[i] = i, ce[i] = cyl[i] = 0, e[i].clear();
// MULTITASK CLEARING END
bool flag = true;
for (int i = 1; i <= n; i++) {
if (!merge(tu[i], tv[i], i)) {
printf("-1\n");
flag = false;
break;
}
}
// continue;
if (!flag) continue;
int ans = 0;
for (int i = 1; i <= m; i++) if (fa[i] == i) {
if (ce[i]) {
// find the cycle
dfs(i, 0);
auto [p, q] = findCycle(tu[ce[i]], tv[ce[i]]);
// calc tree part
for (int i : p) {
ans += solveCycleTree(i, 0);
}
// calc cycle part
int k = p.size();
if (k == 1) {
if (p[0] == su[ce[i]] || p[0] == sv[ce[i]]) {
ans++;
}
} else {
for (int j = 1; j < k; j++) {
cid[j] = q[j - 1], cu[j] = p[j - 1], cv[j] = p[j];
}
cid[k] = ce[i], cu[k] = tv[ce[i]], cv[k] = tu[ce[i]];
int a = 0, b = 0, c = 0;
for (int j = 1; j <= k; j++) {
if ((cu[j] == su[cid[j]] && cv[j] == sv[cid[j]]) ||
(cv[j] == su[cid[j]] && cu[j] == sv[cid[j]])) {
c++;
} else if (cu[j] == su[cid[j]] || cu[j] == sv[cid[j]]) {
a++;
} else if (cv[j] == su[cid[j]] || cv[j] == sv[cid[j]]) {
b++;
}
}
if (a > b) swap(a, b);
if (a + c <= b) ans += a + c;
else ans += (a + b + c) / 2;
}
} else {
// precompute dfn
dcnt = 0;
dfs(i, 0);
st.build();
// fixing & precomputing selection
precompute(i, 0);
// calc ans
fans = 0;
solveTree(i, 0);
ans += fans;
}
}
printf("%d\n", ans);
}
return 0;
}