0
点赞
收藏
分享

微信扫一扫

点分治

福福福福福福福福福 2022-05-03 阅读 80
算法

分治是什么?其原理就是把原问题拆解成几个子问题,分别求解子问题之后再合并出原问题。那么点分治其实就是对一棵树进行拆开,分治。

寻找重心

极端情况下,树会退化成链,那么对于链,我们最不划算的选择分治方式是每次选择链头,那么就会变成递归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)

.

举报

相关推荐

0 条评论