问题相当于最大化最终树的最大匹配。
考虑我们如果确定了一个加入连通块的顺序,使每个连通块的根节点指向之前连通块的某个节点。那么每次加入的新连通块如果原本的最大匹配必定包含根节点,显然加入后不会有额外的增量,随便找一个点作为父亲即可;否则如果之前连通块还有不在最大匹配中的节点(也即最大匹配不满),我们将它作为当前连通块根的父亲,可以有额外
1
1
1的增量。
那么现在算法就很明显了,我们显然会优先加入那些最大匹配必定包含根节点的连通块,然后加入那些没被匹配的点数
>
1
>1
>1的连通块,最后加入没被匹配的点数
=
1
=1
=1的连通块(一定是根)。
求最大匹配的时候可以每次找一个叶子,如果它和它的父亲都不在最大匹配中直接贪心加入,容易证明正确性。这里倒着按DFS序加入即可。
时间复杂度为
O
(
n
)
\mathcal O(n)
O(n)。
#include <bits/stdc++.h>
using namespace std;
vector <int> e[100005],vt[100005];
int p[100005];
void dfs(int x,int t) {
vt[t].push_back(x);
for(int i=0;i<e[x].size();i++)
dfs(e[x][i],t);
}
queue <int> q;
bool vis[100005];
int sum[100005];
int main() {
freopen("hidden.in","r",stdin);
freopen("hidden.out","w",stdout);
int n;
scanf("%d",&n);
for(int i=2;i<=n;i++) {
scanf("%d",&p[i]);
if (p[i]) e[p[i]].push_back(i);
}
int ans=0;
for(int i=1;i<=n;i++)
if (!p[i]) {
dfs(i,i);
for(int j=vt[i].size()-1;j>0;j--) {
int x=vt[i][j];
if (!vis[x]&&!vis[p[x]]) {
vis[x]=vis[p[x]]=1;
ans++;
}
}
for(int j=0;j<vt[i].size();j++)
if (!vis[vt[i][j]]) sum[i]++;
}
for(int i=1;i<=n;i++)
if (!p[i]&&(i==1||vis[i])) {
if (i>1) p[i]=1;
for(int j=0;j<vt[i].size();j++)
if (!vis[vt[i][j]]) q.push(vt[i][j]);
}
for(int i=2;i<=n;i++)
if (!p[i]&&!vis[i]&&sum[i]>1) {
if (!q.empty()) {
p[i]=q.front();
ans++;
q.pop();
}
else {
p[i]=1;
q.push(i);
}
for(int j=1;j<vt[i].size();j++)
if (!vis[vt[i][j]]) q.push(vt[i][j]);
}
for(int i=2;i<=n;i++)
if (!p[i]&&!vis[i]&&sum[i]==1) {
if (!q.empty()) {
p[i]=q.front();
ans++;
q.pop();
}
else {
p[i]=1;
q.push(i);
}
for(int j=1;j<vt[i].size();j++)
if (!vis[vt[i][j]]) q.push(vt[i][j]);
}
printf("%d\n",ans);
for(int i=2;i<=n;i++) printf("%d ",p[i]);
printf("\n");
return 0;
}