• 点分治


    点分治

    点分治是解决树上问题的有效手段,其思想在于对于一颗树按点分治,分成若干子树,根据题目所要求的求解树上的问题。

    主要步骤

    下面以LuoguP3806为例讲解其一般步骤

    1.寻找一颗树的重心

    既然要分治肯定需要选取一个分治中心,根据证明,当选一颗树的重心为分治中心时,其处理子树的复杂度和为最小(O(nlogn))

    所以第一步先寻找重心

    方法很简单 寻找一颗树里最大的儿子最小的必然是树的重心 当然也要考虑父亲方向的大小

    void getroot(int x,int fa)
    {
    	sum[x]=1;
    	for(int k=lnk[x];k;k=las[k])
    	if(nex[k]!=fa&&!flag[nex[k]])
    	{
    		getroot(nex[k],x);sum[x]+=sum[nex[k]];
    		if(sum[nex[k]]>Max[x])Max[x]=sum[nex[k]];
    	}
    	if(TOT-sum[x]>Max[x])Max[x]=TOT-sum[x];
    	if(Max[x]<MAX)root=x,MAX=Max[x];
    }
    

    2.求解相关信息

    这一步所要得到的信息就需要根据各个题目不同去选择,就本题来说,因为要求树上距离为k的点对,所以不妨就求解每个分治得到的子树中各个点到重心的距离。

    void getdis(int x,int fa,int D)
    {
    	dis[++T]=D;
    	for(int k=lnk[x];k;k=las[k])
    	if(nex[k]!=fa&&!flag[nex[k]])getdis(nex[k],x,D+cost[k]);
    }
    

    3.合并信息得到答案

    在上一步得到各个点到重心的距离后,由于树上的路径必然由几段路径拼接而成,所以就先计算路径全部经过重心拼接所得到的答案。(此时计算的是某条路径长度可以被得到的次数,因为后面还需要其他操作)

    对本题来说方法也很简单,就是在对于子树各个点到重心的距离处理完后,枚举每个询问和其中一个距离,用指针从后往前扫出第二个距离是否存在,其对于答案的贡献就是两者的数量相乘。

    void solve(int x,int y,int id)//y和id的意思可以暂时忽略 下文有
    {
    	int t=0;T=0;getdis(x,0,y);
    	sort(1+dis,1+T+dis);
    	t=1;top[1]=dis[1];cnt[1]=1;cnt[0]=1;
    	for(int i=2;i<=T;i++)
            if(dis[i]!=dis[i-1])top[++t]=dis[i],cnt[t]=1;else cnt[t]++;
    	for(int ii=1;ii<=m;ii++)
    	{
    		int k=ask[ii],tail=t;
    		for(int i=0;i<=t;i++)
    		{
    			if(top[i]>k/2)break;
    			if(!k%2&&(top[i]==k/2)){ans[ii]+=id*cnt[i]*(cnt[i]-1)/2;break;}//(对于k/2的情况特殊处理)
    			while(top[tail]+top[i]>k&&i<tail)tail--;
    			if(top[tail]+top[i]==k)ans[ii]+=id*cnt[i]*cnt[tail];
    		}
    	}
    }
    

    经过上面的处理后,其实发现所得到的的答案是有点问题的。

    问题主要在于如果对于两个节点,他们来自重心的同一棵子树,那么计算他们路径长度时其实是不需要经过重心的,但在上面的计算中却被计算了,所以需要减去。

    还是对于上述代码,当需要减去时id=-1且y为重心到儿子的长度 因为所要减去的路径是原有的经过重心的路 这样在getdis的时候自然就会得到原有长度

    具体过程

    #include<bits/stdc++.h>
    #define inf 1000000000
    using namespace std;
    const int maxn=20005;
    int TOT,T,k,tot,n,m,a,b,c,x,Max=inf,root;
    int nex[maxn],las[maxn],lnk[maxn],cost[maxn];
    int ask[maxn],ans[maxn],flag[maxn],mson[maxn],sum[maxn],dis[maxn],top[maxn],cnt[maxn];
    int read()
    {
    	int ch=0,x=0;while(ch=getchar(),ch<'0'||ch>'9');
    	while(x=x*10+ch-48,ch=getchar(),ch>='0'&&ch<='9');
    	return x;
    }
    void add(int x,int y,int z){nex[++TOT]=y;las[TOT]=lnk[x];lnk[x]=TOT;cost[TOT]=z;}
    void getroot(int x,int fa)
    {
    	sum[x]=1;mson[x]=0;
    	for(int k=lnk[x];k;k=las[k])
    	if(nex[k]!=fa&&!flag[nex[k]])
    	{
    		getroot(nex[k],x);
    		sum[x]+=sum[nex[k]];
    		if(sum[nex[k]]>mson[x])mson[x]=sum[nex[k]];
    	}
    	if(tot-sum[x]>mson[x])mson[x]=tot-sum[x];
    	if(mson[x]<Max)Max=mson[x],root=x;
    }
    void getdis(int x,int fa,int D)
    {
    	dis[++T]=D;
    	for(int k=lnk[x];k;k=las[k])
    	if(nex[k]!=fa&&!flag[nex[k]])getdis(nex[k],x,D+cost[k]);
    }
    void solve(int x,int y,int id)
    {
    	int t=0;T=0;getdis(x,0,y);
    	sort(1+dis,1+T+dis);
    	t=1;top[1]=dis[1];cnt[1]=1;cnt[0]=1;
    	for(int i=2;i<=T;i++)if(dis[i]!=dis[i-1])top[++t]=dis[i],cnt[t]=1;else cnt[t]++;
    	for(int ii=1;ii<=m;ii++)
    	{
    		int k=ask[ii],tail=t;
    		for(int i=0;i<=t;i++)
    		{
    			if(top[i]>k/2)break;
    			if(!k%2&&(top[i]==k/2)){ans[ii]+=id*cnt[i]*(cnt[i]-1)/2;break;}
    			while(top[tail]+top[i]>k&&i<tail)tail--;
    			if(top[tail]+top[i]==k)ans[ii]+=id*cnt[i]*cnt[tail];
    		}
    	}
    }
    void divide(int x)//分治过程
    {
    	getroot(x,0);flag[root]=1;solve(root,0,1);//先getroot确定重心 标记并且求解答案
    	for(int k=lnk[root];k;k=las[k])
    	if(!flag[nex[k]])
    	{
    		solve(nex[k],cost[k],-1);//对于子树处理重复答案
    		Max=inf;tot=sum[nex[k]]>sum[root]?sum[nex[k]]-sum[root]:sum[nex[k]];
            //由于需要分治子树 所以对于MAX(求解重心用)和tot都需更改
        	//tot更改是需要注意,由于得到sum数组过程不一定从重心开始
            //所以对于子树的sum[x]>sum[root]时 因为子树在getroot时为root的祖先 tot应为全部的减去sum[root] 否则tot就应该等于sum[x]
    		divide(nex[k]);
    	}
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n-1;i++)a=read(),b=read(),c=read(),add(a,b,c),add(b,a,c);
    	for(int i=1;i<=m;i++)ask[i]=read();
    	tot=n;divide(1);//tot表示当前处理的树的大小
    	for(int i=1;i<=m;i++)
    	if(ans[i]>0)puts("AYE");else puts("NAY");
    	return 0;
    }
    

    点分治在处理树上问题时的效率为(O(nlogn))

  • 相关阅读:
    cisco/CCNA思科静态路由配置(附PKA文件)
    Web前端常用词汇大全
    解决Linux无法安装pygame问题
    CC2530常用的控制寄存器
    解决MySQL外键约束中的引用列和引用列不兼容问题
    详解使用Hyper-V安装Ubuntu Server 16.10
    虚拟机VMware下CentOS6.6安装教程图文详解
    word论文排版技法之五——标题样式关联多级列表
    如何写《软件需求规格说明书》
    VisualStudio官网使用教程
  • 原文地址:https://www.cnblogs.com/DavidJing/p/12514456.html
Copyright © 2020-2023  润新知