点分治

Posted stungyep

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了点分治相关的知识,希望对你有一定的参考价值。

目录

点分治

点分治是一种基于树的重心,统计树上路径的优秀算法。将树上的路径分为经过树的重心和不经过树的重心两种,同时利用树的重心性质,使得递归深度不超过 (logn)次。总的时间复杂度为(nlog^2n)

【题意】:poj_1741 求解一个树上所有边的和不超过k的共有多少个

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>

using namespace std;
const int maxn=1e4+10;
const int inf=0x3f3f3f3f;
int n,k,tot,root,cnt,ans,scale;
int head[maxn],size[maxn],maxson[maxn],dis[maxn];
//size[i]为以i为根的子树的大小,dis[i]为i到根的距离
bool vis[maxn];
struct Edge
{
    int nex,to,val;
}edge[maxn<<1];
void add(int from,int to,int val)
{
    edge[++tot].nex=head[from];
    edge[tot].val=val;
    edge[tot].to=to;
    head[from]=tot;
}
void getroot(int u,int fa)
{
    size[u]=1;
    maxson[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].nex)
    {
        int v=edge[i].to;
        if(v==fa||vis[v])   continue;
        getroot(v,u);
        size[u]+=size[v];
        maxson[u]=max(maxson[u],size[v]);
    }
    maxson[u]=max(maxson[u],scale-maxson[u]);
    if(maxson[u]<maxson[root])  root=u;
}
void getdis(int u,int fa,int val)   //val是u到目标点的距离,fa是u的父亲
{                                   //求出所有点到该子树根节点的距离
    dis[++cnt]=val;
    for(int i=head[u];i!=-1;i=edge[i].nex)
    {
        int v=edge[i].to;
        if(v==fa||vis[v])   continue;
        getdis(v,u,val+edge[i].val);
    }
}
int calc(int u,int val)
{
    cnt=0;
    getdis(u,0,val);
    int l=1,r=cnt,sum=0;
    sort(dis+1,dis+cnt+1);
    while(1)                        //不考虑非法双指正扫描求出满足的边的个数(非法会在后面减去)
    {
        while(r&&dis[l]+dis[r]>k)   r--;
        if(r<l) break;
        sum+=r-l;
        l++;
    }
    return sum;
}
void solve(int u)
{
    ans+=calc(u,0);
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].nex)
    {
        int v=edge[i].to;
        if(vis[v])  continue;
        ans-=calc(v,edge[i].val);   //容斥定理
        root=0;                     //开始分治每一颗子树
        scale=size[v];              //子树的规模
        getroot(v,0);
        solve(root);
    }
}

int main()
{
    while(~scanf("%d %d",&n,&k))
    {
        if(!(n||k)) break;
        tot=ans=0;
        memset(vis,false,sizeof(vis));
        memset(head,-1,sizeof(head));
        for(int i=1;i<n;++i){
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        maxson[0]=inf;
        scale=n;
        getroot(1,0);
        solve(root);
        printf("%d
",ans);     
    }
}

[题意]:洛谷P 3806;m个询问,每个询问输入一个k,求能不能满足有边的权值正好等于k。

点分治所有可能的k都提前打表到数组即可。show code:

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e4+10;
const int inf=0x3f3f3f3f;
int tot,n,m,k,root,cnt,ans[100000010],scale;
int head[maxn],dis[maxn],maxson[maxn],size[maxn];
bool vis[maxn];
struct Edge
{
    int nex,to,val;
}edge[maxn<<1];
void add(int from,int to,int val)
{
    edge[++tot].to=to;
    edge[tot].val=val;
    edge[tot].nex=head[from];
    head[from]=tot;
}
void getroot(int u,int fa)
{
    size[u]=1;
    maxson[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].nex)
    {
        int v=edge[i].to;
        if(v==fa||vis[v])   continue;
        getroot(v,u);
        size[u]+=size[v];
        maxson[u]=max(maxson[u],size[v]);
    }
    maxson[u]=max(maxson[u],scale-maxson[u]);
    if(maxson[u]<maxson[root])  root=u;
}
void getdis(int u,int fa,int val)
{
    dis[++cnt]=val;
    for(int i=head[u];i!=-1;i=edge[i].nex)
    {
        int v=edge[i].to;
        if(v==fa||vis[v])   continue;
        getdis(v,u,val+edge[i].val);
    }
}
void calc(int u,int val,int num)        //num为1或-1,因为判断是不是非法
{
    cnt=0;
    getdis(u,0,val);
    for(int i=1;i<=cnt;++i)
        for(int j=1;j<=cnt;++j)
            if(i!=j)    ans[dis[i]+dis[j]]+=num;
}
void solve(int u)
{
    calc(u,0,1);
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].nex)
    {
        int v=edge[i].to;
        if(vis[v])      continue;
        calc(v,edge[i].val,-1);
        root=0;
        scale=size[v];
        getroot(v,0);
        solve(root);
    }
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i){
        head[i]=-1,vis[i]=false;
    }
    tot=0;
    for(int i=1;i<n;++i){
        int a,b,val;
        scanf("%d %d %d",&a,&b,&val);
        add(a,b,val);
        add(b,a,val);
    }
    maxson[0]=inf;
    scale=n;
    getroot(1,0);
    solve(root);
    while(m--)
    {
        scanf("%d",&k);
        ans[k]?printf("AYE
"):printf("NAY
");
    }
    system("pause");
}

以上是关于点分治的主要内容,如果未能解决你的问题,请参考以下文章

基于点分治的树分治

POJ 1741 Tree ——点分治

bzoj2599 [ IOI2011] -- 点分治

算法有关点分治的一些理解与看法

POJ 3714 分治/求平面最近点对

分治——最近点对