分治是什么?其原理就是把原问题拆解成几个子问题,分别求解子问题之后再合并出原问题。那么点分治其实就是对一棵树进行拆开,分治。
寻找重心
极端情况下,树会退化成链,那么对于链,我们最不划算的选择分治方式是每次选择链头,那么就会变成递归n次。
所以我们每次分治都要尽可能地使一棵树被均匀分开,而所找的这个分治点就是树的重心。
树的重心指删除该节点后得到的最大子树的节点数最少。
例如k=3;
A为重心,0 1 1 2 2 2
cal(B)时:dst[B]=1 dst[C]=2 dst[D]=2
#include<bits/stdc++.h>
#define MAXN 10005
#define LL long long
using namespace std;
int n,K;//题目给出
int Rot,RotSize;//重心,当前子树大小
int Siz[MAXN];//Siz[x]为以x为根的子树大小
int dst[MAXN];//路径长度
int Maxs[MAXN];//Maxs[x]为删去x后的子树中最大子树大小
LL Ans;//答案
vector<int> Now;//路径
bool vis[MAXN];
struct Edge{//邻接表存边
int tot,lnk[MAXN],son[MAXN<<1],nxt[MAXN<<1],W[MAXN<<1];
void clean()
{
memset(lnk,0,sizeof(lnk));
tot=0;
}
void Add(int x,int y,int z)
{
son[++tot]=y;
W[tot]=z;
nxt[tot]=lnk[x];
lnk[x]=tot;
}
}E;
void Get_Rot(int x,int fa)
{
Siz[x]=1;//在没有遍历这棵树之前,我们认为x这棵树已经有一个节点了
Maxs[x]=0;//而删去x后的子树中最大子树大小为0
for(int j=E.lnk[x];j;j=E.nxt[j])
if(!vis[E.son[j]]&&E.son[j]!=fa)
{
Get_Rot(E.son[j],x);
Siz[x]+=Siz[E.son[j]];//x这棵树的大小要加上它所有子树的大小
Maxs[x]=max(Siz[E.son[j]],Maxs[x]);//每次判断x的最大子树
}
Maxs[x]=max(Maxs[x],RotSize-Siz[x]);//在判断是不是重心比较的是所有子树的最大值最小
if(Maxs[x]<Maxs[Rot])
Rot=x;
}
void Get_Dst(int x,int f)
{
Now.push_back(dst[x]);
for(int j=E.lnk[x];j;j=E.nxt[j])
if(E.son[j]!=f&&!vis[E.son[j]])
{
dst[E.son[j]]=dst[x]+E.W[j];
Get_Dst(E.son[j],x);
}
}
int Cal(int x,int y)//返回所有距离小于等于k的点对个数
{
int Ret=0;
Now.clear();
dst[x]=y;
Get_Dst(x,0);//得到所有点到x的距离
sort(Now.begin(),Now.end());
for(int l=0,r=Now.size()-1;l<r;l++)
{
while(Now[l]+Now[r]>K && l<r)
r--;
Ret+=r-l;
}
return Ret;
}
void Solve(int x)//参数为重心
{
Ans+=Cal(x,0);//第一个参数为重心,第二个参数为初始距离
vis[x]=1;//标记该点已被分治过
for(int j=E.lnk[x];j;j=E.nxt[j])
if(!vis[E.son[j]])
{
Ans-=Cal(E.son[j],E.W[j]);
Maxs[0]=RotSize=Siz[E.son[j]];
Rot=0;
Get_Rot(E.son[j],0);
Solve(Rot);
}
}
int main()
{
while(scanf("%d%d",&n,&K),n||K)
{
memset(vis,0,sizeof(vis));
E.clean();
int x,y,z;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
E.Add(x,y,z);
E.Add(y,x,z);
}
Ans=0;
Maxs[0]=RotSize=n;
Rot=0;
Get_Rot(1,0);//找到以1为根的这棵树的重心
Solve(Rot);//求出Ans
printf("%lld\n",Ans);
}
return 0;
}
时间复杂度
每次分治都将树的重心作为分治点,每次划分后子树大小减半,所以递归树的高度为O(logn)。
对距离数组排序的时间复杂度为O(n logn),所以总时间复杂度为O(n long^2 n)
.