题意
给出 \(n\) 个人的性别 \(b_i\)、喜欢的人 \(a_i\)(有且仅有一个)。
现在两人分一组,若一组中存在一人喜欢另一人,则称这一组为「满意」的。
要求在最大化「满意」组数的前提下最大化男女同桌组数,并构造分组方案。
思路
考虑建图,从 \(i\) 到 \(a_i\) 连一条有向边,转化为基环树上最大匹配。
我们任选一条环上的边,将它删去,跑一遍树上DP。唯一可能比它优的情况是保留这条边,删去一条与它相邻的环边。所以跑两次DP取最优值即可。
正确性:如果当前删除的这条边在最优解里面,那么与它相邻的环边一定不在最优解里面。
-
找环:发现这是一棵基环内向树,任何一个点沿着唯一出边走都会走到环上,可以随便选一个点直接循环找到。
-
拆边DP:建反图,这样可以保证对于任意点,它唯一的入边来自它的父亲,方便转移。
令 \(dp_{u,1/0}\) 表示以 \(u\) 为根节点的子树中,\(u\)被/不被匹配时能获得的最大价值,\(dp_{u,0}\) 显然为 \(u\) 各子树最大价值之和,\(dp_{u,1}\) 则需要钦定一个子节点 \(v\) 与 \(u\) 匹配,单独算 \(v\) 的贡献。于是有:
\[dp_{u,0} = \sum\limits_{v \in son_u}{\max(dp_{v,0}, dp_{v,1})} \]\[dp_{u,1} = \max\limits_{v \in son_u}{(dp_{u,0}-\max(dp_{v,0}, dp_{v,1})+dp_{v, }+val(u,v))} \]处理一组匹配的贡献 \(val(u,v)\) 大致有两种做法:
- 把「满意」组数、男女同桌组数装在结构体里,重载比较、合并的运算符。
- 令贡献为首要权值乘一个基数 \(B\) 再加上次要权值(\(B\) 大于次要权值值域即可),最终答案分别为 \(\left\lfloor\dfrac{ans}{B}\right\rfloor\)、\(ans \bmod B\)。
我用的是第二种。构造方案就记录每个状态从哪里转移来即可,注意这道题DFS可能会爆栈。
Code
#include <bits/stdc++.h>
#define IL inline
#define vec basic_string
#define QAQ pair<ll, vec<Node_t>>
using namespace std;
using ll = long long;
const int N = 1e6 + 5, B = 1e9;
vector<int> g[N];
int a[N], b[N], vis[N], pre[N]; ll dp[N][2];
struct Node_t {
int first, second;
IL bool operator<(const Node_t &o) const {
return first == o.first ? second < o.second : first < o.first;
}
};
IL QAQ bfs(int rt) {
queue<int> q; q.push(rt);
vec<int> Q; Q.push_back(rt);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) if (v != rt) q.push(v), Q.push_back(v);
}
for (int i = Q.size() - 1; i >= 0; i--) {
int u = Q[i];
vis[u] = 1, dp[u][0] = dp[u][1] = pre[u] = 0;
for (int v : g[u]) if (v != rt) dp[u][0] += max(dp[v][0], dp[v][1]);
for (int v : g[u]) if (v != rt) {
ll _ = dp[u][0] - max(dp[v][0], dp[v][1]) + dp[v][0] + B + (b[u] != b[v]);
if (_ > dp[u][1]) dp[u][1] = _, pre[u] = v;
}
}
vec<Node_t> res;
for (int u : Q) if (dp[u][1] > dp[u][0] && pre[u]) res.push_back({u, pre[u]}), pre[pre[u]] = 0;
return {max(dp[rt][0], dp[rt][1]), res};
}
IL QAQ operator+(const QAQ & a, const QAQ & b) {
return {a.first + b.first, a.second + b.second};
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T; cin >> T;
while (T--) {
int n; cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i], g[a[i]].push_back(i);
}
QAQ ans;
for (int i = 1, p, q; i <= n; i++) if (!vis[i]) {
p = i; while (!vis[a[p]]) vis[p] = 1, p = a[p]; q = a[p];
ans = ans + max(bfs(p), bfs(q));
}
cout << ans.first / B << ' ' << ans.first % B << '\n';
for (auto & p : ans.second) cout << p.first << ' ' << p.second << '\n';
for (int i = 1; i <= n; i++) g[i].clear(), vis[i] = 0;
}
return 0;
}
典
P1352 没有上司的舞会:树上最大独立集
Vijos1144 小胖守皇宫:树上最小支配集
P8655 [蓝桥杯 2017 国 B] 发现环:基环树找环板子,可用 tarjan / 并查集 / 拓扑排序
P9869 [NOIP2023] 三值逻辑:建图 + 找环
P1453 城市环路:基环树拆边DP板题
P2607 [ZJOI2008] 骑士:P1453双倍经验
P3651 展翅翱翔之时 (はばたきのとき):基环树上贪心
P4381 [IOI2008] Island:基环树森林直径之和
reference:
基环树学习指南 —— White_Sheep
P9869 | 你真的会基环树找环吗 —— Moeebius
[题解]P4381 [IOI2008]Island —— marTixx