1 条题解
-
0
自动搬运
来自洛谷,原作者为

Alex_Wei
**搬运于
2025-08-24 22:46:00,当前版本为作者最后更新于2023-04-12 20:38:26,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
P9170 [省选联考 2023] 填数游戏
在 和 之间连边,选择每条边的一端使得选择的点互不相同,则任意连通块满足 ,否则无解。
每条边 根据 有四种状态:什么都不选,只选 ,只选 ,两个都可以选。若 ,则这条边一定不会产生贡献。若 ,则 Alice 一定选择交集里的元素。若 ,则 Alice 可以选择任何一个元素。
对于 ,连通块的形态是基环树。
对于不在环上的边,Bob 只能选叶向端,因此如果一条边的状态是只选叶向端或两个都可以选,则 Alice 一定会让这条边产生贡献。
对于环上的边:
- 环大小为 ,则只要 Alice 可以选就一定产生贡献。
- 环大小大于 ,则有两种定向方案。Bob 一定选代价较小的那个,Alice 的目标是让代价较小的方案的代价最大化。设 分别表示只能选 ,只能选 ,两个都可以选的边数,相当于求 。不妨设 ,则当 时,贡献为 ,否则贡献为 。
对于 ,连通块的形态是树。
Bob 有 种策略:不选连通块中的某个点。设 表示不选 的代价,则 Alice 给一条边定向的影响形如给一侧子树的 加 ,求 的最大值。
现在我们只关心未定向的边该如何定向。考虑任意两条未定向的边,设它们分别给 的 加 。若 和 无交,我们可以把这两条边都反向,将 变成 。这样至少给每个 加了 ,一定不劣。
因此,任意两条未定向的边的 有交,则所有 的交非空。枚举交集中的某个点 ,则所有未定向的边的方案是确定的:以 为根的叶向。这样,对应的 等于 ,其中 表示 到 的根向边的数量,减去 到 的叶向边的数量。
直接枚举 并计算 ,时间复杂度 。
换根时维护以 为根的 ,以及 子树内所有 的 的最小值。需要预处理以初始 为根,每个结点 子树内所有 的 的最小值和次小值。具体细节根据父亲走向子节点时 和 的变化讨论一下即可。
时间复杂度 。
#include <bits/stdc++.h> using namespace std; using ll = long long; bool Mbe; constexpr int N = 1e6 + 5; int n, m, s1[N], s2[N]; struct edge { int to, w, id; // w = -1: useless // w = 0: choose this // w = 1: choose to // w = 2: any }; vector<edge> e[N]; int V, E, vv[N], ve[N], deg[N]; vector<int> c; void dfs(int id) { V++, vv[id] = 1, c.push_back(id); for(auto _ : e[id]) { int it = _.to; if(!ve[_.id]) E++, ve[_.id] = 1; if(!vv[it]) dfs(it); } } namespace Cyc { // ez int solve() { int ans = 0, on = -1; queue<int> q; for(int id : c) { deg[id] = e[id].size(); if(deg[id] == 1) q.push(id); else on = id; } while(!q.empty()) { // let's toposort! int t = q.front(); q.pop(), V--; for(auto _ : e[t]) { int it = _.to; if(deg[it] > 1) { ans += _.w == 0 || _.w == 2; if(--deg[it] == 1) q.push(it); else on = it; } } } if(V == 1) { for(auto _ : e[on]) { int it = _.to; if(on == it) { ans += _.w >= 0; break; } } } else { int c0 = 0, c1 = 0, c2 = 0; int cur = on, lst = 0; while(V--) { for(auto _ : e[cur]) { int it = _.to; if(deg[it] == 1) continue; if(_.id == lst) continue; if(_.w == 0) c0++; if(_.w == 1) c1++; if(_.w == 2) c2++; cur = it, lst = _.id; break; } } if(c0 > c1) swap(c0, c1); if(c0 + c2 <= c1) ans += c0 + c2; else ans += (c0 + c1 + c2) / 2; } return ans; } } namespace Tree { // 2 hard 4 me int f[N], g[N], mn[N], smn[N], gmn[N]; void dfs1(int id, int ff) { f[id] = g[id] = mn[id] = smn[id] = gmn[id] = 0; for(auto _ : e[id]) { int it = _.to; if(it == ff) continue; dfs1(it, id); f[id] += f[it]; if(_.w > 0) f[id]++; int v = mn[it]; if(_.w == 0) v++; if(_.w > 0) v--; if(v < mn[id]) smn[id] = mn[id], mn[id] = v; else if(v < smn[id]) smn[id] = v; } } void dfs2(int id, int ff) { gmn[id] = min(gmn[id], 0); for(auto _ : e[id]) { int it = _.to; if(it == ff) continue; g[it] = g[id] + f[id] - f[it] - (_.w > 0); if(_.w == 0 || _.w == 2) g[it]++; int v = mn[it]; if(_.w == 0) v++; if(_.w > 0) v--; gmn[it] = min(gmn[id], v == mn[id] ? smn[id] : mn[id]); if(_.w == 0 || _.w == 2) gmn[it]--; if(_.w == 1) gmn[it]++; dfs2(it, id); } } int solve() { dfs1(c[0], 0), dfs2(c[0], 0); int ans = 0; for(int id : c) ans = max(ans, f[id] + g[id] + min(mn[id], gmn[id])); return ans; } } void solve() { cin >> n >> m; for(int i = 1; i <= m; i++) e[i].clear(), vv[i] = 0; for(int i = 1; i <= n; i++) { ve[i] = 0; int sz; cin >> sz >> s1[i]; if(sz == 1) s2[i] = -1; else cin >> s2[i]; } for(int i = 1; i <= n; i++) { int sz, x, y = -1; cin >> sz >> x; auto add = [&](int u, int v, int w) { e[u].push_back({v, w, i}); }; if(sz == 1) { if(s1[i] == x || s2[i] == x) add(x, x, 0), add(x, x, 0); else add(x, x, -1), add(x, x, -1); } else { cin >> y; if(s2[i] == -1) { if(s1[i] == x) add(x, y, 0), add(y, x, 1); else if(s1[i] == y) add(x, y, 1), add(y, x, 0); else add(x, y, -1), add(y, x, -1); } else { if(s1[i] > s2[i]) swap(s1[i], s2[i]); if(x > y) swap(x, y); if(s1[i] == x && s2[i] == y) add(x, y, 2), add(y, x, 2); else if(s1[i] == x || s2[i] == x) add(x, y, 0), add(y, x, 1); else if(s1[i] == y || s2[i] == y) add(x, y, 1), add(y, x, 0); else add(x, y, -1), add(y, x, -1); } } } int ans = 0; for(int i = 1; i <= m; i++) { if(e[i].empty() || vv[i]) continue; V = E = 0, c.clear(), dfs(i); if(V < E) return cout << "-1\n", void(); if(V == E) ans += Cyc::solve(); else ans += Tree::solve(); } cout << ans << "\n"; } bool Med; int main() { fprintf(stderr, "%.4lf\n", (&Mbe - &Med) / 1048576.0); #ifdef ALEX_WEI FILE* IN = freopen("game.in", "r", stdin); FILE* OUT = freopen("game.out", "w", stdout); #endif ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int T; cin >> T; while(T--) solve(); cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n"; return 0; } /* g++ game.cpp -o game -std=c++14 -O2 -DALEX_WEI */
- 1
信息
- ID
- 8553
- 时间
- 2000ms
- 内存
- 1024MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者