点分治算法是和树链剖分同样重要的图论算法。洛谷上有两道模板题,这里均提供代码和详细解释。

所谓点分治,就是对与一棵树,求满足条件的点对有多少对(维护的信息为点对间的最短路径上的信息)。

如何分治呢?

首先,我们将一棵无根树变成有根数,当然每次分治时要选择树的重心以达到效率最高。

树的重心:最小化所选节点的最大子树的大小。

分治策略:先处理所有最短路经过重心的点对,然后删除重心(打上标记即可),对于每棵子树,重复找重心和处理点对的操作。

具体实现见代码。

洛谷P3806点分治模板1:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<climits>
#include<ctime>
#include<queue>
#include<vector>
#include<map>
#include<algorithm>
#include<iomanip>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
typedef long long LL;
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
const int M=10005;
int cnt=2,to[M<<1],next[M<<1],head[M],w[M<<1],siz[M],n,m,sum;
const int maxk=10000001;
int MIN,root;
bool ok[maxk],vis[M];
int tot,top[M],p[M];
void insert_edge(int x,int y,int z){
    to[cnt]=y;
    w[cnt]=z;
    next[cnt]=head[x];
    head[x]=cnt++;
    to[cnt]=x;
    w[cnt]=z;
    next[cnt]=head[y];
    head[y]=cnt++;
}
void getroot(int u,int fa){//找重心,先随便选一个节点为备选的根
    siz[u]=1;
    int MAX=0;
    for(int i=head[u];i;i=next[i]){
        int v=to[i];
        if(!vis[v]&&v!=fa){
            getroot(v,u);
            siz[u]+=siz[v];
            MAX=max(MAX,siz[v]);//比较所有子树的大小
        }
    }
    MAX=max(MAX,sum-siz[u]);//若选择节点u为根,则将总节点数减去该子树大小即得到包含备选根在内的最后一棵子树的大小
    if(MAX<MIN)MIN=MAX,root=u;//最小化最大值
}
void getdeep(int u,int fa,int deep){//计算重心到当前节点的信息
    for(int i=head[u];i;i=next[i]){
        int v=to[i];
        if(!vis[v]&&v!=fa){
            p[++tot]=deep+w[i];//将待处理的所有子树上的点映射到一位数组中
            top[tot]=u==root?v:top[u];//记录当前节点属于哪一棵子树
            ok[p[tot]]=1;//处理重心和当前节点组成的点对的信息
            getdeep(v,u,p[tot]);
        }
    }
}
void solve(int u){
    tot=0;
    getdeep(u,u,0);
    rep(i,1,tot-1)
    rep(j,i+1,tot)
    if(top[i]!=top[j])ok[p[i]+p[j]]=1;//当且仅当两个点不属于同一棵子树,则该点对的最短路径才经过重心
    vis[u]=1;//删除重心(打标记)
    for(int i=head[u];i;i=next[i]){
        int v=to[i];
        if(!vis[v]){
            MIN=INT_MAX;
            sum=siz[v];
            getroot(v,v);//分治处理每一棵子树
            solve(root);
        }
    }
}
int main(){
    n=read();m=read();
    rep(i,1,n-1){
        int a=read(),b=read(),c=read();
        insert_edge(a,b,c);
    }
    sum=n;
    MIN=INT_MAX;
    getroot(1,1);
    solve(root);
    while(m--){
        int k=read();
        if(ok[k])printf("AYE\n");else printf("NAY\n");
    }
    return 0;
}

 洛谷P2634点分治模板2:

#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<climits>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
typedef long long LL;
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
const int M=20005;
int t[3],siz[M],to[M<<1],head[M],next[M<<1],w[M<<1],vis[M],pos[M],s[M<<4][3],top[M];
int n,cnt=2,MIN,root,start,end,ans,sum;
void insert_edge(int x,int y,int z){
    to[cnt]=y;
    w[cnt]=z;
    next[cnt]=head[x];
    head[x]=cnt++;
    to[cnt]=x;
    w[cnt]=z;
    next[cnt]=head[y];
    head[y]=cnt++;
}
#define down for(int i=head[u];i;i=next[i])//偷懒^_^
void getroot(int u,int fa){//同模板1
    siz[u]=1;int MAX=0;
    down{
        int v=to[i];
        if(!vis[v]&&v!=fa){
            getroot(v,u);
            siz[u]+=siz[v];
            MAX=max(MAX,siz[v]);
        }
    }
    MAX=max(MAX,sum-siz[u]);
    if(MAX<MIN)MIN=MAX,root=u;
}
void getdeep(int u,int fa,int deep){
    down{
        int v=to[i];
        if(!vis[v]&&v!=fa){
            int deep1=(deep+w[i])%3;//所有的距离信息直接保存对3取模的结果
            if(!deep1)ans+=2;//处理其中一个点是重心的点对信息(一对不同的点被计算了两次)
            if(u==root){
                pos[v]=++end;//记录每棵子树映射到的数组中的位置,这里用start和end两个指针来保存每次处理信息的数组区间,这样保证每次处理信息后不用将数组初始化
                s[end][deep1]++;//表示保存在end位置的子树中距离为deep1重心为deep1(已对3取模)的点计数
                top[v]=v;//记录当前点所在子树(用重心的儿子节点来表示)
            }
            else{
                top[v]=top[u];
                s[pos[top[v]]][deep1]++;
            }
            getdeep(v,u,deep1);
        }
    }
}
void solve(int u){
    getdeep(u,u,0);
    t[0]=t[1]=t[2]=0;//初始化非常重要!第一遍WA了就是这里没有初始化qwq
    rep(i,start+1,end)
    rep(j,0,2)t[j]+=s[i][j];//计算出该次求解过程中与重心距离为i的节点共有多少个
    rep(i,start+1,end)ans+=s[i][0]*(t[0]-s[i][0])+s[i][1]*(t[2]-s[i][2])+s[i][2]*(t[1]-s[i][1]);//同一棵子树中的点对不计数,需要减掉
    vis[u]=1;
    down{
        int v=to[i];
        if(!vis[v]){
            sum=siz[v];//分治后的节点总数
            start=end;//更新指针(相当于舍去start位置以前的所有信息)
            MIN=INT_MAX;
            getroot(v,v);
            solve(root);
        }
    }
}
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
int main(){
    n=read();
    rep(i,1,n-1){
        int x=read(),y=read(),z=read();
        insert_edge(x,y,z);
    }
    sum=n;MIN=INT_MAX;
    getroot(1,1);
    solve(root);
    ans+=n;
    int g=gcd(n*n,ans);
    printf("%d/%d\n",ans/g,n*n/g);
    return 0;
}
//其实用计数的方法求出点对数目后,每次求一个连通块的时间变成了线性,所以总的时间复杂度应该是点分治的复杂度O(nlogn),洛谷的数据范围给弱了