题解: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;
}

浙公网安备 33010602011771号