题目描述
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
输入输出格式
输入格式:
n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径
接下来m行每行询问一个K
输出格式:
对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)
输入输出样例
输入样例1:
2 1
1 2 2
2
输出样例1:
AYE
说明
对于30%的数据n≤100
对于60%的数据n≤1000,m≤50
对于100%的数据n≤10000,m≤100,c≤1000,K≤10000000
观察题目,很显然,这是一道点分治的模板题。
那么,我们就用点分治的方法解决它吧。
但是,点分治究竟是一种什么样的办法呢?
首先,就个人理解, 点分治是一种将树不断拆分处理的办法。而拆分树,我们则需要选择一个分治点,将树分成几部分分别处理。
我们该选择什么点进行拆分呢?
就像快排一样,如果选择的”中点”不优,快排的复杂度可以达到O(n^2),而点分治中,分治点如果选择的是端点,则复杂度会变成O(n)。
当然,快排的过程中,并不会一个一个数取检索来保证它选取的数一定是中间值,不过人们也可能会选择随机取数的方法来保证它的复杂度在一个较低的情况。
树的重心:
对于一颗n个结点的无根树,找到一个点,使得把树变成以该点为根得有根树时,最大子树的结点数最小。换句话说,删除这个点后最大联通块的结点数最小,那么这个点就是树的重心。
树的中心:
给出一棵边带权的树,求树中的点,使得此点到树中的其他结点的最远距离最近。
我们发现,如果我们能够得到树的重心,那么我们就能够以树的重心为分治点,让被切割后的所有子树尽可能平均,因为此点的最大子树的结点数最小。
那么,点分治的第一个重要操作,就是求取一个树(或者一颗被切割后的子树)的重心位置。
解法:任选一个结点为根,把无根树变成有根树,然后设f[i]表示以i为根的子树的结点个数。不难发现f[i]=Σf[j] (j∈s[i]) + 1 。程序实现很简单:只需要一次DFS,在无根树转有根树的同时同步计算即可。其实在删除结点i后,最大的联通块有多少个结点呢?结点i的子树中最大的有max{f[j]}个结点,i的”上方子树”中有n-f[i]个结点。
当然,我们也可以并不仅仅使用一个f[i]数组存储结点以i为根的子树的结点个数。因为这样的话效率相对可能会有所降低。另外增添一个数组记录g[i]表示以i为根的最大子树的结点个数,那么我们就可以得到g[i]=max{f[j]} ( j 是 i 的儿子) 然后再比较 g[i] 和 i 的祖先及其余部分的结点个数(也就是除了以i为根的子树以外的其他树的部分),就可以得到 g[i] 表示出以i为根的最大子树的结点个数。
通过比较所有的 g[i] ,也可以在 DFS(深度优先搜索)的过程中比较,来得到当前树的重心。
接着,我们就开始点分治的第二个重要环节。
点分治运用了容斥的思想。
我们使用点分治时,在寻找到重心后,将其作为分治点,先从分治点开始遍历一遍所有的点,并且计算出该树所有点距离分治点的大小,然后将它们两两组合记录下来,换句话说