CF963B 树的删除
1 CF963B 树的删除
2 题目描述
给你一棵树(一个有 \(n\) 个顶点和 \(n-1\) 边的图,在这个图中,可以只使用它的边从任何其他顶点到达任何顶点)。
如果这个顶点具有偶数度,则顶点可以被删除。如果删除掉一个顶点,连接到它的所有边也会被删除。
如果可能,给出删除给定树中所有顶点的顺序。
3 题解
解决这道题的切入点在于树:这道题完全可以出在正常的图上,我们为啥非得要在树上解决问题呢?我们发现,给出树后,我们在图的任意部分处理时,只要确定了节点数,一定能确定出边的数量。
由这一点出发,我们发现:任意子树可以通过节点数的奇偶性分成两种,奇树和偶树。如果整个树是一颗偶树的话,边数为奇数。由于每次只能删除偶数条边,我们肯定不能将整个树删除。如果一个子树是奇树的话,那么这个树一定由三部分构成:若干个偶树,根节点,偶数个奇树。删除时,我们必须先删除偶树,因为如果我们先删除奇树,会导致根节点被删除:奇树可能会退化成一个点的形式,要想删除这个点就必须删除根节点。而偶数在删除完毕后可能会退化成两个点形式,这时删除其中与根节点有连线的点(有 \(2\) 条 连线,为偶数条,可以删除),可以完整地将偶树删除。(注意:这里可以删除偶树,主要是借助了根节点与偶树的连边,这就是为什么单独的一个偶树无法被删除,偶子树却可以被删除)在删除完偶子树后,与根结点相连的边便只有偶数条了(每个奇子树贡献一条)。因此,我们可以把根节点删除,剩下的偶数个奇树就可以单独根据上述过程删除了。
如果我们遍历到的子树是偶子树,我们采取的删法一样,但证明过程略有不同。偶树肯定也是由三部分组成的:若干个偶树,根节点,奇数个奇树。我们在删除完偶树后,发现此时奇数个奇子树会与根节点有奇数条连线,不满足连线为偶数条这一条件。但是,我们在上面说过,如果我们删除的是偶树,那么需要借助的是根节点的帮助:加上与更上一级的根节点的连线后,根节点的连线条数成功变为偶数条,可以删除!为什么我们在上面删除奇树时就没有与更上一级的根节点的连线呢?这是因为,我们是先删除的根节点再删除的奇子树。在删除奇子树是根节点已经不复存在了。
综上所述:我们只用按照偶子树,根节点,奇子树的方法删除即可完美删除掉可以被删除的树。
4 代码(空格警告):
#include <iostream>
#include <queue>
using namespace std;
const int N = 2e5+10;
int n, p;
int tot;
int head[2*N], ver[2*N], last[2*N], sz[N];
void add(int x, int y)
{
ver[++tot] = y;
last[tot] = head[x];
head[x] = tot;
}
void init(int x, int fa)
{
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (fa == y) continue;
init(y, x);
sz[x] += sz[y];
}
}
void dfs(int x, int fa)
{
queue<int> even, odd;
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (y == fa) continue;
if (sz[y] % 2 == 0) even.push(y);
else odd.push(y);
}
while (even.size())
{
dfs(even.front(), x);
even.pop();
}
cout << x << endl;
while (odd.size())
{
dfs(odd.front(), x);
odd.pop();
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> p;
if (!p) continue;
add(i, p);
add(p, i);
}
for (int i = 1; i <= n; i++) sz[i] = 1;
if (n % 2 == 0)
{
cout << "NO";
return 0;
}
else cout << "YES" << '\n';
init(1, -1);
dfs(1, -1);
return 0;
}