点分治模板

luogu_3806

近些日子学了点分治,当然只是学了个模板。

所谓点分治,使用于处理树上路径的一种分治手段。因为利用了重心的性质,时间复杂度可以保证呢。

所谓算法流程

  1. 选取当前子树的重心

  2. 计算路径总数,不管路径是否过当前重心(后面会去重)

  3. 计算起点和终点在同一颗子树中的合法路径(因为这条路径不是简单路径),并在答案中减去

  4. 将子树剔除出来,并在子树中重复此过程

需要注意的点

在处理路径(也就是深度时),一定要重新计算子节点个数(因为根有可能转变)

时间复杂很玄学
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>

using std::sort;
using std::max;

const int maxn=10100;
const int inf=0x7fffffff;

struct node
{
    int p;
    int nxt;
    int v;
};

node line[maxn<<1];
int head[maxn],tail;
int dep[maxn],f[maxn],size[maxn],vis[maxn],root,sum;
int d[maxn],tot;
int ans[10000011];

void add(int a,int b,int c)
{
    line[++tail].p=b;
    line[tail].v=c;
    line[tail].nxt=head[a];
    head[a]=tail;
    return ;
}

void get_hry(int now,int fa)
{
    int v;
    f[now]=0;size[now]=1;
    for(int i=head[now];i;i=line[i].nxt)
    {
        v=line[i].p;
        if(vis[v]||v==fa)   continue;
        get_hry(v,now);
        size[now]+=size[v];
        f[now]=max(f[now],size[v]);
    }
    f[now]=max(f[now],sum-size[now]);//最后和他上面的节点数取个最大值
    if(f[root]>f[now])  root=now;
}

void get_dep(int now,int fa)//处理路径,这时一定要重新计算size
{
    int v;d[++tot]=dep[now];
    size[now]=1;
    for(int i=head[now];i;i=line[i].nxt)
    {
        v=line[i].p;
        if(v==fa||vis[v])   continue;
        dep[v]=dep[now]+line[i].v;
        get_dep(v,now);
        size[now]+=size[v];
    }
    return ;
}

void calc(int now,int Dep,int delta)//核心的计算函数
{
    dep[now]=Dep;tot=0;
    get_dep(now,0);
    sort(d+1,d+1+tot);
    for(int i=1;i<=tot;i++)
        for(int j=1;j<=tot;j++) if(i!=j)
            ans[d[i]+d[j]]+=delta;//桶
    return ;
}

void solve(int now)
{
    vis[now]=1;//断开连接
    calc(now,0,1);
    for(int i=head[now];i;i=line[i].nxt)
    {
        int v=line[i].p;
        if(vis[v])  continue;
        calc(v,line[i].v,-1);
        root=0;sum=size[v];//计算子树的重心
        get_hry(v,0);
        solve(root);
    }
    return ;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1,a,b,c;i<n;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);add(b,a,c);
    }
    root=0;sum=n;//root,和sum分别是当前联通块内的根和节点数
    f[root]=inf;//操作一波
    get_hry(1,0);
    solve(root);//点分治
    for(int i=1,k;i<=m;i++)
    {
        scanf("%d",&k);
        if(ans[k])  printf("AYE\n");
        else    printf("NAY\n");
    }
    return 0;
}

posted @ 2019-01-09 17:38  Lance1ot  阅读(102)  评论(0编辑  收藏  举报