本题当然可以通过大力讨论每棵子树的size的大小关系,然后用各种数据结构暴力维护。但是我更倾向于用一种更为性质的做法。

首先讲一下我在考场上想到的做法(没写)。就是考虑换根,在换根的过程中计算每一条边删去后得到的两棵子树的重心, 由于重心的一些性质,如果我们把以点v的所有儿子为根的子树的重心求了出来,那么我们要求以点v为根的子树的重心时,可以保证重心一定在v的重儿子子树的重心到v的链上,那么我们就可以通过链上倍增来实现对多棵子树的重心的合并。在换根时维护每个点子树size,以及子树内的重心,换根时倍增求新子树的重心即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 300007
#define M 600007
#define ll long long
#define mem(x) memset(x,0,sizeof(x))
int hd[N],pre[M],to[M],num,wa[N],wb[N],sz[N],ts,dep[N];
int anc[N][23],ms[N],n;
ll ans; void clear()
{
mem(hd),mem(wa),mem(wb),mem(sz);
mem(dep),mem(ms);
num=0;ans=0;
} int calc(int v)
{
return max(ms[v],ts-sz[v]);
}
void solve(int x,int d,int &a,int &b)
{
if(dep[x]<d)return;
for(int i=20;i>=0;i--)
{
int mid=anc[x][i],y=anc[mid][0];
if(dep[y]>=d&&calc(mid)>=calc(y))
x=mid;
}
int y=anc[x][0];
int dx=calc(x),dy=calc(y);
if(dep[x]==d||dx<dy)
{
if(dx<=ts/2)a=x;
}
else
{
if(dy<=ts/2)
{
a=y;
if(dx==dy)b=x;
}
}
}
/*void solve(int x,int d,int &a,int &b)
{
while(dep[x]>=d)
{
if(calc(x)<=ts/2)
{
if(a)b=x;
else a=x;
}
x=anc[x][0];
}
}*/
int glca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--)
if(dep[x]-dep[y]>=(1<<i))
x=anc[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(anc[x][i]!=anc[y][i])
x=anc[x][i],y=anc[y][i];
return anc[x][0];
}
int dmax(int x,int y)
{
return dep[x]>dep[y]?x:y;
}
int dmin(int x,int y)
{
return dep[x]<dep[y]?x:y;
}
void dfs(int v,int f)
{
dep[v]=dep[f]+1;
anc[v][0]=f;
for(int i=1;i<=20;i++)
anc[v][i]=anc[anc[v][i-1]][i-1];
sz[v]=1;
int son=v;
for(int i=hd[v];i;i=pre[i])
{
int u=to[i];
if(u==f)continue;
dfs(u,v);
sz[v]+=sz[u];
if(sz[u]>ms[v])ms[v]=sz[u],son=u;
}
ts=sz[v];
int x=( son==v ? v : dmax(wa[son],wb[son]) );
solve(x,dep[v],wa[v],wb[v]);
}
void dp(int v,int f)
{
int s1,d1,s2,d2;
s1=d1=s2=d2=0;
for(int i=hd[v];i;i=pre[i])
{
int u=to[i];
if(sz[u]>s1)s2=s1,d2=d1,s1=sz[u],d1=u;
else if(sz[u]>s2)s2=sz[u],d2=u;
}
for(int i=hd[v];i;i=pre[i])
{
int u=to[i];
if(u==f)continue;
int x=d1;
if(u==d1)x=d2; int _ms=ms[v],_wa=wa[v],_wb=wb[v]; sz[v]-=sz[u],sz[u]+=sz[v];
ms[v]=sz[x];
wa[v]=wb[v]=0;
ts=sz[v]; int p=dmax(wa[x],wb[x]);
//if(p==0)printf("WA\n");
if(x==f)
{
int q=dmin(wa[x],wb[x]);
int lca=glca(p,v);
if(q!=0&&q==anc[lca][0])p=q,lca=p;
solve(p,dep[lca],wa[v],wb[v]);
solve(v,dep[lca]+1,wb[v],wa[v]);
}
else
{
solve(p,dep[v],wa[v],wb[v]);
} /*if(!wa[v]&&!wb[v])
{
printf("%d\n",v);
printf("%d\n",x);
}*/
ans+=wa[v]+wb[v]+wa[u]+wb[u];
dp(u,v); wa[v]=_wa,wb[v]=_wb;
ms[v]=_ms;
sz[u]-=sz[v],sz[v]+=sz[u];
} }
void adde(int x,int y)
{
num++;pre[num]=hd[x];hd[x]=num;to[num]=y;
}
void dewa()
{
for(int i=1;i<=n;i++)
printf("%d: %d %d\n",i,wa[i],wb[i]);
} int main()
{
//freopen("data.in","r",stdin);
int t;
scanf("%d",&t);
while(t--)
{
clear();
int x,y;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
adde(x,y),adde(y,x);
}
dfs(1,0);
//dewa();
dp(1,0);
printf("%lld\n",ans);
}
return 0;
}

还有一种题解做法,也是利用重心的性质,但是比我的更巧妙,而且实现也更简单。

就是考虑以某一个点v为根时怎么找它的重心,可以发现从点v出发,到重心的路径一定是不断沿着重儿子走,而每一个点的重儿子是唯一的,那么我们用倍增优化沿着重儿子走的过程,设f[i][j]为从点i开始,走\(2^j\)步走到的位置,我们只要找到最后一个满足\(2*size[v]\geq sum\)的点即可,其中sum是割边后当前连通块的大小。同样像前一种做法一样换根即可,注意f[i][j]是可以换根维护的。

题解做法:

#include<bits/stdc++.h>
using namespace std;
#define N 300007
#define M 600007
#define ll long long
#define mem(x) memset(x,0,sizeof(x))
int hd[N],pre[M],to[M],num,son[N],fa[N],sz[N];
int ch[N][23],n;
ll ans;
void clear()
{
mem(hd),mem(son);
ans=0,num=0;
}
void adde(int x,int y)
{
num++;pre[num]=hd[x];hd[x]=num;to[num]=y;
}
void mak_st(int v)
{
for(int i=1;i<=20;i++)
ch[v][i]=ch[ch[v][i-1]][i-1];
}
void dfs(int v,int f)
{
fa[v]=f,sz[v]=1;
for(int i=hd[v];i;i=pre[i])
{
int u=to[i];
if(u==f)continue;
dfs(u,v);
sz[v]+=sz[u];
if(sz[u]>sz[son[v]])son[v]=u;
}
ch[v][0]=son[v];
mak_st(v);
}
void solve(int x,int ts)
{
for(int i=20;i>=0;i--)
{
int y=ch[x][i];
if(y&&2*sz[y]>=ts)x=y;
}
ans+=x;
if(2*sz[x]==ts)ans+=fa[x];
}
void dp(int v,int f)
{
int s1,s2;
s1=s2=0;
for(int i=hd[v];i;i=pre[i])
{
int u=to[i];
if(sz[u]>sz[s1])s2=s1,s1=u;
else if(sz[u]>sz[s2])s2=u;
}
for(int i=hd[v];i;i=pre[i])
{
int u=to[i];
if(u==f)continue;
int x=s1;
if(u==s1)x=s2; sz[v]-=sz[u];
ch[v][0]=x;
mak_st(v); solve(v,sz[v]),solve(u,sz[u]); sz[u]+=sz[v],fa[v]=u; dp(u,v); sz[u]-=sz[v],sz[v]+=sz[u];
}
ch[v][0]=son[v];
mak_st(v);
fa[v]=f;
}
int main()
{
//freopen("data.in","r",stdin);
int t;
scanf("%d",&t);
while(t--)
{
clear();
scanf("%d",&n);
int x,y;
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
adde(x,y),adde(y,x);
}
dfs(1,0),dp(1,0);
printf("%lld\n",ans);
}
return 0;
}

比较两种做法,发现其实都利用的是重儿子的性质,只不过我的做法是从重儿子子树内往根走,而题解做法是从根往重儿子走。为什么题解做法比我的短了很多呢?

因为我的倍增数组是从下往上倍增,换根时根的改变会导致下面所有子孙结点的倍增数组改变,于是不能在换根过程中维护,所以我的倍增数组一直是以1号点为根的,就多了当前点的重儿子是从以1为根时的父亲方向来还是从儿子方向来的讨论。而题解是从上往下倍增的,换根时不会导致下面的点发生改变,而能够自上往下倍增的原因又是题解利用了每个点只会往唯一的重儿子走的性质,那么每个点出发走若干步走到的点是唯一的,就可以倍增了。

我在洛谷的题解中,看到了一篇大力讨论子树size的题解,里面的一些计数技巧还是有点意思的,比如说把计算每条边割断后子树重心标号之和转化为,对每一个点计算它会在多少条边被割断之后成为子树的重心,大家可以看一看。Mr_Wu 的博客

CSP2019 树的重心 题解的更多相关文章

  1. [CSP-S2019]树的重心 题解

    CSP-S2 2019 D2T3 考场上扔了T2来打这题的部分分,然后没看到数据范围是等号,不知道怎么判完全二叉树然后40分滚粗…… ---- 思路分析 很容易想到$O(n^2)$每次暴力找重心,这个 ...

  2. 题解-FJOI2014 树的重心

    FJOI2014 树的重心 \(Q\) 组测试数据.给一棵树大小为 \(n\),求有多少个子树与其重心相同.重心可能有多个. 数据范围:\(1\le Q\le 50\),\(1\le n\le 200 ...

  3. CSP2019 Day2T3 树的重心

    显然如果我们直接枚举断哪条边,剩下的两颗树的重心编号非常不好求,一个常见的想法是我们反过来,考虑每个节点有多少种情况作为树的重心,计算每个点对答案的贡献. 下面我们就需要大力分类讨论了.假设我们现在考 ...

  4. POJ 1655 求树的重心

    POJ 1655 [题目链接]POJ 1655 [题目类型]求树的重心 &题意: 定义平衡数为去掉一个点其最大子树的结点个数,求给定树的最小平衡数和对应要删的点.其实就是求树的重心,找到一个点 ...

  5. 洛谷P3345 [ZJOI2015]幻想乡战略游戏(动态点分治,树的重心,二分查找,Tarjan-LCA,树上差分)

    洛谷题目传送门 动态点分治小白,光是因为思路不清晰就耗费了不知道多少时间去gang这题,所以还是来理理思路吧. 一个树\(T\)里面\(\sum\limits_{v\in T} D_vdist(u,v ...

  6. 点分治模板(洛谷P4178 Tree)(树分治,树的重心,容斥原理)

    推荐YCB的总结 推荐你谷ysn等巨佬的详细题解 大致流程-- dfs求出当前树的重心 对当前树内经过重心的路径统计答案(一条路径由两条由重心到其它点的子路径合并而成) 容斥减去不合法情况(两条子路径 ...

  7. POJ 1655 - Balancing Act - [DFS][树的重心]

    链接:http://poj.org/problem?id=1655 Time Limit: 1000MS Memory Limit: 65536K Description Consider a tre ...

  8. BZOJ 3510 - 首都 「 $LCT$ 动态维护树的重心」

    这题 FlashHu 的优化思路值得借鉴 前置引理 树中所有点到某个点的距离和中,到重心的距离和是最小的. 把两棵树通过某一点相连得到一颗新的树,新的树的重心必然在连接原来两棵树重心的路径上. 一棵树 ...

  9. poj 1655 树的重心

    Balancing Act Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 13178   Accepted: 5565 De ...

随机推荐

  1. 使用OpenSSL证书操作详解

    一.OpenSSL简介 OpenSSL支持多种秘钥算法,包括RSA.DSA.ECDSA,RSA使用比较普遍.官网地址:https://www.openssl.org/,一般CeontOS系统都装有Op ...

  2. 物联网架构成长之路(39)-Bladex开发框架环境搭建

    0.前言 上一篇博客已经介绍了,阶段性小结.目前第一版的物联网平台已经趋于完成.框架基本不变了,剩下就是调整一些UI,还有配合硬件和市场那边,看看怎么推广这个平台.能不能挣点外快.第一版系统虽然简陋, ...

  3. Allure+pytest 生成测试报告

    简介: python 主流自动化测试报告插件有三个:HTMLTestRunner.BeautifulReport 和 Allure.HTMLTestRunner是一个比较古老的报告模板,界面也不是很好 ...

  4. git commit 提交失败

    git commit -m 'xxx' 报错 报错信息 当前分支:master 远程分支:gitlib.xxx error: cannot spawn .git/hooks/commit-msg: N ...

  5. Knative 应用在阿里云容器服务上的最佳实践

    作者|元毅 阿里云智能事业群高级开发工程师 相信通过前面几个章节的内容,大家对 Knative 有了初步的体感,那么在云原生时代如何在云上玩转 Knative?本篇内容就给你带来了 Knative 应 ...

  6. 【git】git命令集合

    [在包含.git目录所在的项目根目录下,打开git Bash] 参考地址:https://www.cnblogs.com/sxdcgaq8080/p/11655170.html =========== ...

  7. Java自学-集合框架 遍历

    遍历ArrayList的三种方法 步骤 1 : 用for循环遍历 通过前面的学习,知道了可以用size()和get()分别得到大小,和获取指定位置的元素,结合for循环就可以遍历出ArrayList的 ...

  8. 遍历倒排索引核心类:SegmentTermDocs/SegmentTermPositions

    查询有哪些文档包含某个词元是Lucene搜索非常基础的一个功能,上层的搜索功能和索引功能都要基于这个功能来搭建.SegmentTermDocs就是查询词元所属文档的核心类,SegmentTermPos ...

  9. Eureka获取服务列表源码解析

    在之前的文章:EurekaClient自动装配及启动流程解析中,我们提到了在类DiscoveryClient的构造方法中存在一个刷新线程和从服务端拉取注册信息的操作 这两个就是eureka获取服务列表 ...

  10. nginx缓存页面后,串会话问题的解决方案

    用的Nigix  后面挂了二个Tomcat是springMVC  session存在Redis的项目 但是上线以后反应A用户添加数据,变成B用户的,网上查的方案如下: 解决方案,nginx提供prox ...