题目链接:
- link,点击这里喵。
题意简述:
- 给出一棵树,取一点 i i i,并另取两点 u u u 和 v v v,在 u u u 和 v v v 简单路径上任取一点使这点与 i i i 的距离最小,计算出这样所有的组合的距离的总和。
思路:
- 看眼数据范围,总点数 n ≤ 2 × 1 0 5 n\le2\times10^5 n≤2×105,考虑线性算法,有关于树,首先考虑树上 dp。
- 这道题题面很具有迷惑性,她把 ∑ u = 1 n ∑ v = u + 1 n ∑ i = 1 n f ( u , v , i ) \sum\limits_{u = 1}^n \sum\limits_{v = u + 1}^n \sum\limits_{i = 1}^n f(u, v, i) u=1∑nv=u+1∑ni=1∑nf(u,v,i) 中的 $\sum\limits_{i = 1}^n $ 放在了后面,这就导致如果思路想到枚举 u u u 和 v v v,就很难 dp 了,根据 ∑ \sum ∑ 的性质,我们把 $\sum\limits_{i = 1}^n $ 提到外面去,原式就变成了 ∑ i = 1 n ∑ u = 1 n ∑ v = u + 1 n f ( u , v , i ) \sum\limits_{i = 1}^n \sum\limits_{u = 1}^n \sum\limits_{v = u + 1}^n f(u, v, i) i=1∑nu=1∑nv=u+1∑nf(u,v,i),思路就简洁明了了,记录每个点的答案,最后相加即可。
- 那么 O ( n ) O(n) O(n) 做法考虑换根 dp,这里假设以点 1 1 1 为出发点。
做法:
-
定义 l j i lj_i lji 为点 i i i 及其子树所包含的点对数量, s i z i siz_i sizi 为点 i i i 及其子树的大小, s i z i siz_i sizi 为以点 i i i 为 ∑ u = 1 n ∑ v = u + 1 n f ( u , v , i ) \sum\limits_{u = 1}^n \sum\limits_{v = u + 1}^n f(u, v, i) u=1∑nv=u+1∑nf(u,v,i) 的答案。
dfs1:
-
s
i
z
n
o
w
=
∑
t
o
s
i
z
t
o
siz_{now}=\sum\limits_{to} siz_{to}
siznow=to∑sizto (
t
o
to
to 为
n
o
w
now
now 的子节点)
//这个应该不必多言 -
l
j
n
o
w
=
∑
t
o
l
j
t
o
+
∑
t
o
∑
t
o
2
≠
t
o
s
i
z
t
o
×
s
i
z
t
o
2
+
(
s
i
z
n
o
w
−
1
)
lj_{now}=\sum\limits_{to} lj_{to} + \sum\limits_{to}\sum\limits_{to2 \ne to} siz_{to} \times siz_{to2} + (siz_{now}-1)
ljnow=to∑ljto+to∑to2=to∑sizto×sizto2+(siznow−1)(
t
o
,
t
o
2
to,to2
to,to2 为
n
o
w
now
now 的子节点)
∑ t o l j t o \sum\limits_{to} lj_{to} to∑ljto 应该很容易理解,加上子树点对数。
∑ t o ∑ t o 2 ≠ t o s i z t o × s i z t o 2 \sum\limits_{to}\sum\limits_{to2 \ne to} siz_{to} \times siz_{to2} to∑to2=to∑sizto×sizto2 就是乘法原理,每个子树有多少个可以选择的,互相乘起来就好,这里这么写方便,实际应当去除重复计算的。
( s i z n o w − 1 ) (siz_{now}-1) (siznow−1) 为当前节点与子树上每个节点所构成的点对。 -
a
n
s
n
o
w
=
∑
t
o
(
a
n
s
t
o
+
l
j
t
o
)
ans_{now}=\sum\limits_{to} (ans_{to}+lj_{to})
ansnow=to∑(ansto+ljto)
l j t o lj_{to} ljto 便是原先所有点对数量,由于回到了 n o w now now,单子树整个的花费比原来节点多了 l j t o × 1 lj_{to}\times 1 ljto×1,总花费便是 ( a n s t o + l j t o ) (ans_{to}+lj_{to}) (ansto+ljto) - 代码实现:
void dfs1(int now,int fa){ siz[now]=1; for(auto to: G[now]){ if(to==fa){continue;} dfs1(to,now); siz[now]+=siz[to]; ans[now]+=ans[to]+lj[to]; lj[now]+=lj[to]; mod(lj[now]);mod(ans[now]); } lnt sum=0; for(auto to: G[now]){if(to==fa){continue;}sum+=siz[to];}//这里进行了优化,把O(n^2)的枚举优化成O(n) for(int i=0;i<G[now].size();++i){ //其实就是计算了个总和而已啦 int to=G[now][i]; if(to==fa){continue;} sum-=siz[to]; lj[now]+=siz[to]*sum; } lj[now]+=siz[now]-1; lj[now]%=MOD; }
dfs2:
那么有了公式就好推了,仍然从 1 1 1 出发。
枚举 n o w now now 的子节点 t o to to,由先前所推的公式易得:- 不算子树 t o to to 时, l j n o w n e w = l j n o w − l j t o − s i z t o × ( s i z n o w − s i z t o − 1 ) − s i z t o lj_{nownew}=lj_{now}-lj_{to}-siz_{to}\times (siz_{now}-siz_{to}-1)-siz_{to} ljnownew=ljnow−ljto−sizto×(siznow−sizto−1)−sizto
- 同理可知不算子树 t o to to 时, a n s n o w t e m p = a n s n o w − a n s t o − l j t o ans_{nowtemp}=ans_{now}-ans_{to}-lj_{to} ansnowtemp=ansnow−ansto−ljto
- 故可知,加入
n
o
w
now
now 为子树的
t
o
to
to 有:
l j t o n e w = ( s i z n o w − s i z t o ) × ( s i z t o − 1 ) + l j n o w n e w + s i z n o w − s i z t o lj_{tonew}=(siz_{now}-siz_{to})\times (siz_{to}-1)+lj_{nownew}+siz_{now}-siz_{to} ljtonew=(siznow−sizto)×(sizto−1)+ljnownew+siznow−sizto
a n s t o n e w = a n s t o + a n s n o w − a n s t o − l j t o + l j n o w n e w ans_{tonew}=ans_{to}+ans_{now}-ans_{to}-lj_{to}+lj_{nownew} anstonew=ansto+ansnow−ansto−ljto+ljnownew
s i z t o = n siz_{to}=n sizto=n - 代码实现:
void dfs2(int now,int fa){ anst+=ans[now]; mod(anst); for(auto to: G[now]){ if(to==fa){continue;} lnt A=lj[now]-lj[to]-siz[to]*(siz[now]-siz[to]-1)-siz[to]; mod(A); ans[to]+=ans[now]-ans[to]-lj[to]+A; mod(ans[to]); lj[to]+=(siz[now]-siz[to])*(siz[to]-1)+A+siz[now]-siz[to]; mod(lj[to]); siz[to]=n; dfs2(to,now); } }
-
s
i
z
n
o
w
=
∑
t
o
s
i
z
t
o
siz_{now}=\sum\limits_{to} siz_{to}
siznow=to∑sizto (
t
o
to
to 为
n
o
w
now
now 的子节点)
小贴士:
- 记得开 longlong,乘法会溢出。
- 记得判断父亲节点,不然会 RE。
时间复杂度分析
- 每个点只在 dfs1 和 dfs2 中分别出现一遍,故时间复杂度时线性 O ( n ) O(n) O(n) 的。
代码:
#include <stdio.h>
#include <ctype.h>
#include <algorithm>
#include <string.h>
#include <vector>
#define lnt long long
#define inf 0x3f3f3f3f
using namespace std;
int xx;char chh,ff;int read(){
xx=ff=0;while(!isdigit(chh)){if(chh=='-'){ff=1;}chh=getchar();}
while(isdigit(chh)){xx=(xx<<1)+(xx<<3)+chh-'0';chh=getchar();}return ff? -xx: xx;
}
const int N=1e6,MOD=1e9+7;
lnt n,ans[N],anst,siz[N],lj[N];//
vector<lnt> G[N];
void dfs1(int,int);
void dfs2(int,int);
void mod(lnt &x){x%=MOD;}
int main(){
n=read();
for(int i=1;i<n;++i){
int x=read(),y=read();
G[x].push_back(y);
G[y].push_back(x);
}
dfs1(1,0);
dfs2(1,0);
printf("%lld",(anst+MOD)%MOD);
return 0;
}
void dfs1(int now,int fa){
siz[now]=1;
for(auto to: G[now]){
if(to==fa){continue;}
dfs1(to,now);
siz[now]+=siz[to];
ans[now]+=ans[to]+lj[to];
lj[now]+=lj[to];
mod(lj[now]);mod(ans[now]);
}
lnt sum=0;
for(auto to: G[now]){if(to==fa){continue;}sum+=siz[to];}
for(int i=0;i<G[now].size();++i){
int to=G[now][i];
if(to==fa){continue;}
sum-=siz[to];
lj[now]+=siz[to]*sum;
}
lj[now]+=siz[now]-1;
lj[now]%=MOD;
}
void dfs2(int now,int fa){
anst+=ans[now];
mod(anst);
for(auto to: G[now]){
if(to==fa){continue;}
lnt A=lj[now]-lj[to]-siz[to]*(siz[now]-siz[to]-1)-siz[to];
mod(A);
ans[to]+=ans[now]-ans[to]-lj[to]+A;
mod(ans[to]);
lj[to]+=(siz[now]-siz[to])*(siz[to]-1)+A+siz[now]-siz[to];
mod(lj[to]);
siz[to]=n;
dfs2(to,now);
}
}