点分治 模板题

题目描述
给定一棵有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(深度优先搜索)的过程中比较,来得到当前树的重心。


接着,我们就开始点分治的第二个重要环节。
点分治运用了容斥的思想。
我们使用点分治时,在寻找到重心后,将其作为分治点,先从分治点开始遍历一遍所有的点,并且计算出该树所有点距离分治点的大小,然后将它们两两组合记录下来,换句话说,它实际上把这棵树变成了一棵以分治点为根,其它所有结点都直接连接在分治点上的树,也就是说,这棵树被看作成一个深度为2的树(只有分治点有儿子)。
然后,用所有点到分治点的距离,算出所有点(也包括分治点本身)间的距离,并加入到数组中。
当然,求取结点间的距离可以运用排序的方法,这样可以更快的筛去超过题设给的最大范围的情况,减少运算次数。
而这样做,则会出现子树上的结点距离边长的情况。例如,一棵树上有一个结点距离分治点dis[i],它的儿子结点距离分治点dis[j],它们之间的距离应当是dis[j]-dis[i],但是我们通过把它看成一棵深度是2的多叉树的情况,将它们的距离算成了dis[j]+dis[i]。
所以,我们需要将这种dis[j]+dis[i]的情况删去,这样,我们因为选取了分治点,将这棵树分成了几部分。那么我们就可以删去分治点,重新求取这棵子树的重心,然后作为分治点继续求取距离,就可以求到dis[j]-dis[i]的情况了。当然,求取到这种情况的时候,已经和一开始的分治点没有关系了。
那么,我们怎么删去同一棵子树的结点间的距离呢?
很显然,我们从分治点开始,提前记录下它的儿子i到分治点的距离dis[i],然后像求分治点到其它结点的距离一样,求出结点i的所有儿子到结点i的距离并且添上2×dis[i],然后把得到的这个距离在数组中删去。
这样就可以删去同一棵子树的结点间的重复情况了。删去分治点后,继续对这个分治点的所有子树求取重心,不断重复操作,就可以得到答案了。
代码如下:

#include 

using namespace std;

const int maxn=10010;
bool vis[maxn];
int treest[maxn],h[maxn],remind[maxn],deep[maxn],dis[maxn];
int n,m,root,tot,sum;
int num[10000010];
struct edge{
    int to,next,value;
}e[maxn<<1];

inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while ((c!='-')&&((c<'0')||(c>'9')))
        c=getchar();
    if (c=='-')
        f=-1,c=getchar();
    while ((c>='0')&&(c<='9'))
    {
        x=(x<<3)+(x<<1)+c-'0';
        c=getchar();
    }
    return x*f;
}

void getroot(int ,int );
void qsort(int ,int );
void divide(int );
void getdeep(int ,int );

int main()
{
    n=read();
    m=read();
    for (int i=1,u,v,w;i0]=sum=n;
    getroot(1,0);
    divide(root);
    for (int i=1;i<=m;i++)
    {
        int p;
        p=read();
        if (num[p])
            printf("AYE\n");
        else
            printf("NAY\n");
    }
    return 0;   
}

void getdeep(int now,int fa)
{
    dis[++tot]=deep[now];
    for (int jump=h[now];jump;jump=e[jump].next)
    {
        int t=e[jump].to;
        if (t==fa||vis[t])
            continue;
        deep[t]=deep[now]+e[jump].value;
        getdeep(t,now);
    }
    return ;
}

void calculate(int now,int dep,int add)
{
    tot=0;
    deep[now]=dep;
    getdeep(now,0);
    for (int i=1;i<=tot;i++)
        for (int j=1;j<=tot;j++)
            num[dis[i]+dis[j]]+=add;
    return ;
}

void divide(int now)
{
    calculate(now,0,1);
    vis[now]=true;
    for (int jump=h[now];jump;jump=e[jump].next)
    {
        int t=e[jump].to;
        if (vis[t])
            continue;
        calculate(t,e[jump].value,-1);
        root=0;
        sum=remind[t];
        getroot(t,0);
        divide(root);
    }
    return ;
}

void getroot(int now,int fa)
{
    remind[now]=1;
    treest[now]=0;
        for (int jump=h[now];jump;jump=e[jump].next)
    {
        int t=e[jump].to;
        if (t==fa||dis[t])
            continue;
        getroot(t,now);
        remind[now]+=remind[t];
        treest[now]=max(treest[now],remind[t]);
    }
    treest[now]=max(treest[now],sum-remind[now]);
    if (treest[now]return ;
}

void qsort(int l,int r)
{
    int a=l,b=r;
    int c=dis[(a+b)/2];
    while (a<=b)
    {
        while (dis[a]while (dis[b]>c)
            b--;
        if (a<=b)
        {
            int f=dis[a];
            dis[a]=dis[b];
            dis[b]=f;
            a++;
            b--;
        }
    }
    if (aif (lreturn ;
}

你可能感兴趣的:(点分治 模板题)