题解:P7026 [NWRRC2017] Hidden Supervisors

posted on 2024-10-03 04:31:56 | under | source

挺厉害一题。

首先题目可以视为:有森林,要将其它树逐个合并到 \(1\) 所在树上,求最大匹配?记这些树为 \(T_1\dots T_m\)\(T_1\)\(1\) 所在的树。

然后考虑通过贪心解决,注意我们不反悔,\(T_1\) 的匹配边一旦确认就不改变,所以要注意后效性。设想一下合并 \(T_1,T_i\),记将它们合并在一起的边是 \(E\),对 \(E\) 是否是匹配边进行讨论:假如不是,那么 \(T_1,T_i\) 保持原有匹配方案即可;否则,则要求两者原有方案中,\(E\) 的两端点不可是匹配点。

于是,在合并过程中,只要关心 \(T_1\) 还未匹配的点的数量就好了。记 \(T_1\) 最大匹配为 \(ans\),未匹配点数 \(cnt=|T_1|-2\times ans\)。可以发现,无论 \(E\) 是不是匹配边,都应该选择 \(T_i\) 满足条件的匹配方案中最大的一种。这是因为假如我相较于最大方案少选 \(k\) 条边,那么目的肯定是让 \(cnt\) 变大 \(2k\),然后为后续合并提供匹配点,在理想情况下,有 \(2k\) 棵树与这些匹配点相连,提供 \(2k\) 条边,所以贡献为 \(k\)(一开始少选了 \(k\) 条)。可是这 \(2k\) 棵树可以互相匹配,产生 \(k\) 对匹配。两者没有区别,而且我还不能保证一定有 \(2k\) 棵树用来匹配,所以直接用最大方案即可。

再来考虑 \(E\) 是否是匹配边,显然 \(cnt=0\)\(E\) 必然不是。

否则,记 \(T_i\) 中的最大匹配为 \(ans_0\),则钦定其根非匹配点的最大匹配 \(ans_1\) 满足 \(0\le ans_0-ans_1\le 1\)。那么若 \(ans_1<ans_0\),没有必要选择 \(E\),因为合并后最大匹配相同(\(cnt\) 也相同)。反之,选择 \(E\) 会比不选的最大匹配多 \(1\),但同时 \(cnt\)\(2\),类似于必选最大方案的证明,同样可证选择 \(E\) 是更优的。

综上,我们得出了合并的法则,对于 \(T_i\),记 \(cnt_0,cnt_1\) 分别为 \(ans_0,ans_1\) 的未匹配点数:

  • \(ans_0=ans_1\)\(cnt>0\)\(E\) 的另一端为 \(T_1\) 任意未匹配点,\(E\) 为匹配边。然后 \(ans\gets ans+ans_1+1\)\(cnt\gets cnt+cnt_1-2\)

  • 否则:\(E\) 的另一端可为 \(T_1\) 任意一点。\(ans\gets ans+ans_0\)\(cnt\gets cnt+cnt_0\)

现在考虑合并顺序。贪心或 dp 预处理出 \(ans_0,ans_1\),称 \(ans_0=ans_1\) 的是一类树,反之为二类树。二类树莫得选择,先把它们合并到 \(T_1\) 上攒攒 \(cnt\);然后显然按 \(cnt_1\) 从大到小合并进 \(T_1\) 即可,因为可以让 \(T_1\) 的未匹配点尽量多。

补充一下,树的最大匹配的贪心为:按深度从大到小遍历节点(或是每次剥去叶子),能和父亲匹配就匹配。若最大匹配有多种方案则贪心会优先选根不是匹配点的方案,所以 \(ans_0<ans_1\) 只用看贪心时根是否为匹配点即可。

复杂度为 \(O(n\log n)\),开销在排序上,当然可以桶排做到线性。

代码

有点丑,慎看。

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;
int n, pa[N], m, rot[N], ans[N], siz[N], Ans, Siz;
bool vis[N];
vector<int> to[N], node[N], Node;

inline void dfs(int u, int &s, vector<int> &node){
	siz[u] = 1, node.push_back(u);
	for(auto v : to[u]) dfs(v, s, node), siz[u] += siz[v];
	if(!vis[pa[u]] && !vis[u]) vis[u] = vis[pa[u]] = 1, ++s;
}
inline bool pd(int x) {return !vis[x];}
inline bool cmp(int x, int y){
	if(vis[x] == vis[y]) return (siz[x] - 2 * ans[x]) > (siz[y] - 2 * ans[y]);
	return vis[x] < vis[y];
}
int main(){
	cin >> n;
	rot[m = 1] = 1;
	for(int i = 2; i <= n; ++i){
		scanf("%d", &pa[i]), to[pa[i]].push_back(i);
		if(!pa[i]) rot[++m] = i;
	}
	
	vis[0] = 1;
	for(int i = 1, x; i <= m; ++i) x = rot[i], dfs(x, ans[x], node[x]); 
	sort(rot + 2, rot + 1 + m, cmp);
	
	Ans = ans[1], Siz = siz[1]; 
	for(auto i : node[1]) if(!vis[i]) Node.push_back(i);
	for(int i = 2, x; i <= m; ++i){
		x = rot[i];
		if(!vis[x] && 2 * Ans < Siz){
			int y = Node.back(); Node.pop_back();
			pa[x] = y;
			for(auto j : node[x]) if(!vis[j] && j != x) Node.push_back(j);
			Ans += ans[x] + 1, Siz += siz[x];
		}
		else{
			pa[x] = 1;
			for(auto j : node[x]) if(!vis[j]) Node.push_back(j);
			Ans += ans[x], Siz += siz[x];
		}
	}
	cout << Ans << endl;
	for(int i = 2; i <= n; ++i) printf("%d ", pa[i]);
	return 0;
}

posted @ 2026-01-15 08:19  Zwi  阅读(1)  评论(0)    收藏  举报