[题解] BZOJ4203 同桌的你

题意

给出 \(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\) 的贡献。于是有:

\[\begin{aligned} 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,0}+val(u,v))} \end{aligned} \]

处理一组匹配的贡献 \(val(u,v)\) 大致有两种做法:

  1. 把「满意」组数、男女同桌组数装在结构体里,重载比较、合并的运算符。
  2. 令贡献为首要权值乘一个基数 \(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 没有上司的舞会:树上最大独立集
P2458 [SDOI2006] 保安站岗:树上最小支配集
P8655 [蓝桥杯 2017 国 B] 发现环:基环树找环板子,可用 tarjan / 并查集 / 拓扑排序
P9869 [NOIP2023] 三值逻辑:建图 + 找环
P1453 城市环路:基环树拆边DP板题
P2607 [ZJOI2008] 骑士:P1453双倍经验
P3651 展翅翱翔之时 (はばたきのとき):基环树上贪心
P4381 [IOI2008] Island:基环树森林直径之和

reference:

基环树学习指南 —— White_Sheep
P9869 | 你真的会基环树找环吗 —— Moeebius
[题解]P4381 [IOI2008]Island —— marTixx

posted @ 2024-03-26 12:06  wkh2008  阅读(43)  评论(0编辑  收藏  举报