题解 P6591 【[YsOI2020]植树】

· · 题解

这题是一道树上操作题,难度不大。

题目分析

其实就是判断一个多叉树中的各子树大小是否相同。
如果以每个节点为根重构树,复杂度将达到O(n^2),在1\le n\le10^6的情况下显然是不行的,只能拿到Subtask 1的40分。这样的数据规模要求我们把复杂度降到O(n\log n)甚至常数稍大的O(n)
这意味着我们只能执行一次重构树操作,就以节点1为根吧。

我们自造一组强度略高的数据,如下:

7
1 2
3 1
4 2
5 2
4 6
3 7

手算得样例输出:

5 6 7

我们把整棵树画出来即为:

以1为根,计算各结点的子树大小(包含自己):

7 4 2 2 1 1 1

然后让每个节点找自己子结点的子树大小,以及n减去自己大小的值:

4 2
2 1 3
1 5
1 5
6
6
6

我们看到,只有第5,6,7行的数字完全相同,满足条件,所以输出5 6 7
大体思路就是这样,主要还是看代码吧。

代码

首先我们用一个邻接表:

#define N 1000005
#define _for(x,y) for(int i = x;i <= y;i ++)
vector <int> es[N];

初始化:

#include <bits/stdc++.h>
using namespace std;
#define N 1000005
#define _for(x,y) for(int i = x;i <= y;i ++)
vector <int> es[N];
bool root[N];
int d[N],n;
int main()
{
    scanf("%d",&n);
    int u,v;
    _for(1,n - 1)
    {
        scanf("%d%d",&u,&v);
        es[u].push_back(v);
        es[v].push_back(u);
    }
    return 0;
}

这里我们对for循环做了一个重定义,主要是想少打几个字吧。

然后就是模板dfs: ```cpp int dfs(int x,int fa) { int size = es[x].size(),num = 0; root[x] = 1; _for(0,size - 1) if(es[x][i] != fa) { d[x] += dfs(es[x][i],x); } ++ d[x]; return d[x]; } ``` 这里我们用接近$O(n)$的复杂度(每个节点走一次)计算出了各子树的大小,还需要判断能否成为根。 我们使用这个$num$表示比较值,如果$num=0$,直接$num=d[es[x][i]]$(此时已计算完成),否则比较$num$和$d[es[x][i]]$的值,如果不同直接$root[x]=0$。 ```cpp _for(0,size - 1) if(es[x][i] != fa) { d[x] += dfs(es[x][i],x); if(!num) num = d[es[x][i]]; if(num != d[es[x][i]]) root[x] = 0; } ``` 然后还要判断$n-d[x]$是否和$num$相等,这里有两点注意: 1. 如果$num$还是0,即x是叶子结点,直接跳过 2. 如果x=1(根节点)直接跳过 代码: ```cpp if(x != 1 && num && num != n - d[x]) root[x] = 0; ``` 整个dfs过程: ```cpp int dfs(int x,int fa) { int size = es[x].size(),num = 0; root[x] = 1; _for(0,size - 1) if(es[x][i] != fa) { d[x] += dfs(es[x][i],x); if(!num) num = d[es[x][i]]; if(num != d[es[x][i]]) root[x] = 0; } ++ d[x]; if(x != 1 && num && num != n - d[x]) root[x] = 0; return d[x]; } ``` 可能有点复杂,但相较于set去重,这种方法常数小一点,时间较为稳定。 做完dfs以后,跑一遍$n$个结点,$root[i]=1$就输出: ```cpp dfs(1,0); _for(1,n) if(root[i]) printf("%d ",i); ``` 全部代码: ```cpp #include <bits/stdc++.h> using namespace std; #define N 1000005 #define _for(x,y) for(int i = x;i <= y;i ++) vector <int> es[N]; bool root[N]; int d[N],n; int dfs(int x,int fa) { int size = es[x].size(),num = 0; root[x] = 1; _for(0,size - 1) if(es[x][i] != fa) { d[x] += dfs(es[x][i],x); if(!num) num = d[es[x][i]]; if(num != d[es[x][i]]) root[x] = 0; } ++ d[x]; if(x != 1 && num && num != n - d[x]) root[x] = 0; return d[x]; } int main() { scanf("%d",&n); int u,v; _for(1,n - 1) { scanf("%d%d",&u,&v); es[u].push_back(v); es[v].push_back(u); } dfs(1,0); _for(1,n) if(root[i]) printf("%d ",i); return 0; } ``` 多叉树的操作题相对二叉树来说可能较为简单,主要就是dfs要写对。有些常见操作(比如求结点大小,求结点深度)等可以打成模板存好,要用的时候 ~~Ctrl+C+V~~ 可以借鉴一下。 $\mathrm{The\ End.}