题目

描述

题目大意

给你一棵树,每次询问两个点,求出这两个点的子树的重心到其中每个点的距离和。

重心的定义是到其中每个点距离和最小的点(不一定在两棵子树内)


思考历程

想想以前我是怎么求重心的呢……先预处理出sizsizsiz,然后重心有个强大的性质:

将重心看做根节点,则其中的每课子树的大小都小于等于它的一半

然后我就乱搞出了一个方法。

可是我最终发现了一个问题:这不是传统的一棵树,这是两棵子树!

于是我的想法就崩塌了。

尝试其它想法。

设输入的两个点分别为xxx和yyy。

重心应该在xxx或yyy的子树内,或者是在xxx到yyy的路径上。

显然,如果在其它地方,一定有更优解。

首先考虑在xxx到yyy的路径上。

我们用一种思路来思考一下,假如当前在ttt点,然后通过移动到更优的位置来不断更新ttt,直到不能被更新为止。

现在t=xt=xt=x,然后ttt要向yyy移动。显然移动一段距离的贡献为sizx−sizysiz_x-siz_ysizx​−sizy​

然而我们发现这个是一个定值,往同一方向移动就会不停增或减。所以最终一定会移动到xxx点或yyy点。

所以没有必要考虑在xxx到yyy路径上的情况。

设all=sizx+sizyall=siz_x+siz_yall=sizx​+sizy​,即总大小。

考虑从uuu移到儿子vvv的贡献。

对于vvv的子树,距离都会减一,所以贡献为−sizv-siz_v−sizv​

对于vvv的子树之外的地方,距离都会加一,所以贡献为all−sizvall-siz_vall−sizv​

总贡献为all−2sizvall-2siz_vall−2sizv​。

显然如果一直往下走,sizvsiz_vsizv​递减,所以这个东西是递增的。

所以当2sizv>all2siz_v>all2sizv​>all时,往下走要比现在的答案更优。

问题来了,儿子这么多,走哪边?

树链剖分,走重链!

为什么?

其实树链剖分有个性质:对于uuu的轻儿子vvv,满足2sizv&lt;sizu2siz_v&lt;siz_u2sizv​<sizu​

这个结论也是比较好证明的,vvv是轻儿子,所以它子树的大小小于等于重儿子的大小,然后就成立了。

对比一下2sizv&lt;sizu2siz_v&lt;siz_u2sizv​<sizu​和上面的式子2sizv&gt;all2siz_v&gt;all2sizv​>all。

由于sizu&lt;allsiz_u&lt;allsizu​<all,所以轻儿子是一定不能走下去的,只有重儿子才有可能走下去。

所以走重链就可以了。答案在xxx或yyy为开始的重链上。

有了这个结论,也可以知道重心会在更大的那棵子树中。

假设sizx&gt;sizysiz_x&gt;siz_ysizx​>sizy​,显然2sizy&lt;all2siz_y&lt;all2sizy​<all,所以在yyy的子树中下不去!

接下来就好办了,对于每条重链,可以往下倍增,倍增到最后一个满足2sizv&gt;all2siz_v&gt;all2sizv​>all的,沿路统计答案。

显然可以预处理出每个节点的子树中的每个点到根的路径和,设为sumsumsum。显然sumx+len∗sizy+sumysum_x+len*siz_y+sum_ysumx​+len∗sizy​+sumy​为它的初值(len表示xxx到yyy的距离)。

然后就没有然后了。

至于一棵子树包含另一种子树的情况,就不用说了,更简单。

时间复杂度比较优秀:O((n+q)lg⁡n)O((n+q)\lg n)O((n+q)lgn)


总结

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
int n;
int fa[N];
struct EDGE{
int to;
EDGE *las;
} e[N];
int ne;
EDGE *last[N];
long long sum[N];
int dep[N],siz[N],hs[N],dfn[N],nowdfn,top[N];//hs为重儿子
long long ssiz[N];//表示从上到下的siz和
void dfs(int x){
dfn[x]=++nowdfn;
siz[x]=1;
dep[x]=dep[fa[x]]+1;
for (EDGE *ei=last[x];ei;ei=ei->las){
dfs(ei->to);
sum[x]+=sum[ei->to]+siz[ei->to];
siz[x]+=siz[ei->to];
if (siz[ei->to]>siz[hs[x]])
hs[x]=ei->to;
}
}
void dfs2(int x,int t){
ssiz[x]=ssiz[fa[x]]+siz[x];
top[x]=t;
if (hs[x])
dfs2(hs[x],t);
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=hs[x])
dfs2(ei->to,ei->to);
}
int lca(int u,int v){
while (top[u]!=top[v]){
if (dep[top[u]]<dep[top[v]])
swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
int down[N][17];//倍增数组
int main(){
scanf("%d",&n);
for (int i=2;i<=n;++i){
scanf("%d",&fa[i]);
e[++ne]={i,last[fa[i]]};
last[fa[i]]=e+ne;
}
dfs(1);
dfs2(1,1);
for (int i=1;i<=n;++i)
down[i][0]=hs[i];
for (int i=1;i<=16;++i)
for (int j=1;j<=n;++j)
down[j][i]=down[down[j][i-1]][i-1];
int T;
scanf("%d",&T);
while (T--){
int x,y;
scanf("%d%d",&x,&y);
if (siz[x]<siz[y])
swap(x,y);
long long all=siz[x],ans=sum[x];
if (!(dfn[x]<=dfn[y] && dfn[y]<dfn[x]+siz[x])){
all+=siz[y];
ans+=(dep[x]+dep[y]-2*dep[lca(x,y)])*siz[y]+sum[y];
}
for (int i=16;i>=0;--i)
if (all<siz[down[x][i]]*2){
ans+=(all<<i)-2*(ssiz[down[x][i]]-ssiz[x]);
x=down[x][i];
}
printf("%lld\n",ans);
}
return 0;
}

总结

原来树链剖分还可以这么用……

[JZOJ4639] 【NOIP2016提高组A组7.16】Angel Beats!的更多相关文章

  1. JZOJ 【NOIP2016提高A组集训第16场11.15】兔子

    JZOJ [NOIP2016提高A组集训第16场11.15]兔子 题目 Description 在一片草原上有N个兔子窝,每个窝里住着一只兔子,有M条路径连接这些窝.更特殊地是,至多只有一个兔子窝有3 ...

  2. JZOJ 【NOIP2016提高A组集训第16场11.15】SJR的直线

    JZOJ [NOIP2016提高A组集训第16场11.15]SJR的直线 题目 Description Input Output Sample Input 6 0 1 0 -5 3 0 -5 -2 2 ...

  3. NOIP2016提高组解题报告

    NOIP2016提高组解题报告 更正:NOIP day1 T2天天爱跑步 解题思路见代码. NOIP2016代码整合

  4. 5820. 【NOIP提高A组模拟2018.8.16】 非法输入(模拟,字符串)

    5820. [NOIP提高A组模拟2018.8.16] 非法输入 (File IO): input:aplusb.in output:aplusb.out Time Limits: 1000 ms   ...

  5. JZOJ 4732. 【NOIP2016提高A组模拟8.23】函数

    4732. [NOIP2016提高A组模拟8.23]函数 (Standard IO) Time Limits: 1500 ms  Memory Limits: 262144 KB  Detailed ...

  6. 【题解】NOIP2016提高组 复赛

    [题解]NOIP2016提高组 复赛 传送门: 玩具谜题 \(\text{[P1563]}\) 天天爱跑步 \(\text{[P1600]}\) 换教室 \(\text{[P1850]}\) 组合数问 ...

  7. 【题解】NOIP2016 提高组 简要题解

    [题解]NOIP2016 提高组 简要题解 玩具迷题(送分) 用异或实现 //@winlere #include<iostream> #include<cstdio> #inc ...

  8. iOS之UITableView组头组尾视图/标题悬停

    最近笔者在公司的iOS开发中,有一个iOS开发同事跑来问了两个问题:1.给UITableView设置了组头和组尾视图,但是一直显示不出来?2.UITableView的section的header和fo ...

  9. iOS自定义组与组之间的距离以及视图

    iOS自定义组与组之间的距离以及视图 //头视图高度 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(N ...

  10. groups---输出指定用户所在组的组成员,

    groups命令   groups命令在标准输入输出上输出指定用户所在组的组成员,每个用户属于/etc/passwd中指定的一个组和在/etc/group中指定的其他组. 语法 groups(选项)( ...

随机推荐

  1. shell脚本实现GoDaddy中IPv6的动态域名解析(DDNS)_可在路由中运行

    首先谈一下个人的需求,家里路由拿到了运营商的外网ipv6地址,于是想着将路由的各种服务通过DDNS放到外网上来,这才有下面的动态域名解析折腾.废话不多说,进入正题. 1.首先在godaddy中注册域名 ...

  2. Scrapy的初体验

    上一节安装了python2和python3的开发环境 首先第一步:进入开发环境,workon article_spider 进入这个环境: 安装Scrapy,在安装的过程中出现了一些错误:通常这些错误 ...

  3. git学习记录2(远程库管理)

    学习参考地址:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 本编随笔只是自己对 ...

  4. ElasticSearch 索引查询使用指南

    1.检测集群是否健康,我们通常用下面的命令.确保9200端口号可用: http://localhost:9200/_cat/health?v 或者 http://localhost:9200/_clu ...

  5. jquery操作html元素之( 获得内容和属性)

    * jQuery - 获得内容和属性 jQuery DOM 操作 jQuery 中非常重要的部分,就是操作 DOM 的能力. jQuery 提供一系列与 DOM 相关的方法,这使访问和操作元素和属性变 ...

  6. 线性可分SVM中线性规划问题的化简

    在网上找了许多关于线性可分SVM化简的过程,但似乎都不是很详细,所以凭借自己的理解去详解了一下. 线性可分SVM的目标是求得一个超平面(其实就是求w和b),在其在对目标样本的划分正确的基础上,使得到该 ...

  7. python实现一个简单木马!

    一个简单的木马程序 绝大多数的木马程序都是基于Socket来实现的 废话少说直接上代码! 代码: client部分: # -*- coding: UTF-8 -*- import socketimpo ...

  8. <面试题>学习面试

    1.代码中要修改不可变数据会出现什么问题? 抛出什么异常? 代码不会正常运行,抛出 TypeError 异常. # 比如修改元祖.会报错 TypeError: 'tuple' object does ...

  9. uoj21 【UR #1】缩进优化

    题目 题意简介明了,需要找到一个\(T\),最小化 \[\sum_{i=1}^n\left \lfloor \frac{a_i}{T} \right \rfloor+\sum_{i=1}^na_i\% ...

  10. arc098D Xor Sum 2

    题意:给你一个数列,问有多少对(l,r)满足A[l]+A[l+1]+...+A[r]=A[l]^A[l+1]^...^A[r]? 标程: #include<bits/stdc++.h> u ...