• [Noip2019]树的重心


    [Noip2019]树的重心

    一.前言

    ​ 真就一行代码调一天呗……题目链接

    二.思路

    ​ 首先题目暗示的很明显了,要你一条边一条边的拆开然后分开算。那么预估一下时间复杂度 (O(n^2)),还有 t 组数据,芜湖直接起飞。但是,如果我们骚一点,在每次找重心的方式上做一做文章,就可以将时间复杂度降到 (O(nlogn)),而这种骚办法,就是倍增。

    ​ 首先我们要明确几个结论。

    ​ 1.对于一个以 u 为根的子树,这颗子树的重心一定会在 u 的重链上(定义与树链剖分的相同,就是以它的某个儿子为根的子树大小最大时,那个儿子所在的方向),这个可以轻易的得出(设这个重心为 v,则有 (size[u]-size[v]<=lfloordfrac{size[u]}{2} floor),( herefore size[v]>=lfloorfrac{u}{2} floor),所以必然为重儿子),那么寻找重心的方向就已经确定了。

    ​ 2.一个点是重心,那么这个点的重儿子或者父亲也有可能是重心(题目明示)

    ​ 有了这两个结论后,我们试图做出暴力的方法。

    ​ 暴力就是(对于一颗树),我们从它的根节点开始,不断地沿着重儿子的方向去找,直到找到重心为止,很暴力,很简单。而重心的判断条件题目已经给出(O(n^2))ok的。

    ​ 但是我们要倍增奥,我们倍增去寻找一个深度最大的点v使得(size[u]-size[v]<=dfrac{size[u]}{2}),此时的v为这颗树的重心.最深的保证了它是重心的正确性(更浅的有可能儿子的子树大小超过限制),再结合结论 2 ,验一下他的父亲就ok了。然后倍增的方法在于,之前我们是不断沿着重儿子的方向找,现在我们一步跨大点,设 (g[x][i]) 为 x 向着重儿子方向走 (2^i) 步,这样就能 (O(logn)) 找到v。就基本解决了。第一次用一个 dfs 处理出重儿子和 g 就好。

    ​ 然后此题另外一个很复杂的地方在于断开一条边之后,另一边的树换根了。假设断掉的边是 ((u,v)),且深度较浅的是 u ,所以 u 成了另外一个根。对于 v 来说,它可以沿用之前的 dfs 出的数据,很方便。但是 u 就比较离谱。

    ​ 我愿称以 u 为根的那个树,叫反图(在代码中所有反图的数组都有前缀 ba_),很显然的,反图有着与原图十分重合的地方,可以自己画图感受一下,只要重儿子不变,g也是不会变的。由于构造反图与枚举边同时进行,我们使用dfs。

    ​ 在这次dfs中,默认 u 以上的反图全部完毕。于是将 u 接到反图上面去,就会出现爸爸当儿子,儿子当爸爸的局面(就像男生寝室的混乱关系),当然 dfs 完了之后,爸爸还是爸爸,儿子还是儿子……,这里看代码会清晰很多。然后对于 u 在反图上面倍增就好,维护反重儿子和反g。

    三.CODE

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<fstream>
    #include<cmath>
    #include<cstring>
    using namespace std;
    #define m_for(i,x,y) for(int i=x;i<=y;++i)
    int read(){
    	char ch=getchar();
    	int res=0,f=1;
    	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
    	return res*f;
    }
    const int MAXN=299999;
    int t,n;
    int head[MAXN],to[2*MAXN],ne[2*MAXN],tot;
    void add(int x,int y){
    	to[++tot]=y;
    	ne[tot]=head[x];
    	head[x]=tot;
    }
    void build(){
    	int x,y;
    	m_for(i,1,n-1){
    		x=read();y=read();
    		add(x,y);add(y,x);
    	}
    }
    int f[MAXN],size[MAXN],heavy[MAXN],sc_heavy[MAXN],g[MAXN][18];
    void dfs1(int x,int fa){
    	f[x]=fa;size[x]=1;
    	for(int i=head[x],v;i;i=ne[i]){
    		v=to[i];
    		if(v==fa)continue;
    		dfs1(v,x);
    		size[x]+=size[v];
    		if(size[v]>size[heavy[x]])sc_heavy[x]=heavy[x],heavy[x]=v;
    		else if(size[v]>size[sc_heavy[x]])sc_heavy[x]=v;
            //这里需要记录重儿子(heavy)和次重儿子(sc_heavy)在dfs2会用到次重儿子
    	}
    	g[x][0]=heavy[x];
    	for(int i=1;i<=17;++i)g[x][i]=g[g[x][i-1]][i-1];//dp预处理
    }
    int ba_heavy[MAXN],ba_size[MAXN],ba_f[MAXN],ba_g[MAXN][18];
    long long ans;
    long long check(int x,int sum){
    	return 1LL*x*(max(sum-ba_size[x],ba_size[ba_heavy[x]])<=(sum/2));//计算一个点的贡献
    }
    void dfs2(int x,int fa){
    	for(int i=head[x],v;i;i=ne[i]){
    		v=to[i];
    		if(v==fa)continue;
    		ba_size[x]=size[1]-size[v];//反图的大小用原图相减
    		ba_f[x]=0;ba_f[v]=0;//各自为根
    		if(v==heavy[x])ba_heavy[x]=sc_heavy[x];
            //如果对面就是重儿子,那么在反图的重儿子就是原图的次重儿子
    		else ba_heavy[x]=heavy[x];
    		if(ba_size[fa]>ba_size[ba_heavy[x]])ba_heavy[x]=fa;
            //原来的爸爸要变儿子,所以重儿子搞一搞
    		ba_g[x][0]=ba_heavy[x];
    		for(int i=1;i<=17;++i)ba_g[x][i]=ba_g[ba_g[x][i-1]][i-1];
            //dp算反图g
    		int now=x;
            //倍增跑正反图
    		for(int i=17;i>=0;--i)if(ba_size[x]-ba_size[ba_g[now][i]]<=(ba_size[x]/2))now=ba_g[now][i];
    		ans+=check(now,ba_size[x])+check(ba_f[now],ba_size[x]);//检验它和它父亲
    		now=v;
    		for(int i=17;i>=0;--i)if(size[v]-size[g[now][i]]<=(size[v]/2))now=g[now][i];
    		ans+=check(now,size[v])+check(f[now],size[v]);
    		ba_f[x]=v;//爸爸变儿子
    		dfs2(v,x);
    	}
        //它的子树遍历完之后还原成原图
    	ba_f[x]=f[x];
    	ba_g[x][0]=ba_heavy[x]=heavy[x];
    	for(int i=1;i<=17;++i)ba_g[x][i]=ba_g[ba_g[x][i-1]][i-1];
    	ba_size[x]=size[x];
    }
    void clean(){
    	ans=0;tot=0;
    	memset(head,0,sizeof(head));
    	memset(heavy,0,sizeof(heavy));
    	memset(g,0,sizeof(g));
    	memset(ba_g,0,sizeof(ba_g));
    }
    int main(){
    	t=read();
    	while(t--){
    		n=read();
    		clean();//初始化
    		build();//建图
    		dfs1(1,0);//处理原图
    		memcpy(ba_heavy,heavy,sizeof(ba_heavy));//因为反图和原图相似所以copy过来
    		memcpy(ba_size,size,sizeof(ba_size));
    		memcpy(ba_f,f,sizeof(ba_f));
    		memcpy(ba_g,g,sizeof(g));
    		dfs2(1,0);
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    从 MVC 到微服务,技术演变的必经之路
    JBOSS最大连接数配置和jvm内存配置
    数据库原理及应用第7章课后习题答案
    如何实现超高并发的无锁缓存?
    手工清理win7系统C盘的技巧
    SVN版本回退
    在sql server数据库的一个表中如何查询共有多少字段
    软件测试的四个阶段
    sp_change_users_login 'Update_One', '用户名', '登录名';
    讲一讲java异常及自定义异常
  • 原文地址:https://www.cnblogs.com/clockwhite/p/13431616.html
Copyright © 2020-2023  润新知