树上dp的好题,Piggy and Trees思路做法详细解析

题目链接:

  • 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 n2×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=1nv=u+1ni=1nf(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=1nu=1nv=u+1nf(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=1nv=u+1nf(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=tosizto 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=toljto+toto2=tosizto×sizto2+(siznow1) t o , t o 2 to,to2 toto2 n o w now now 的子节点)
      ∑ t o l j t o \sum\limits_{to} lj_{to} toljto 应该很容易理解,加上子树点对数。
      ∑ 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} toto2=tosizto×sizto2 就是乘法原理,每个子树有多少个可以选择的,互相乘起来就好,这里这么写方便,实际应当去除重复计算的。
      ( s i z n o w − 1 ) (siz_{now}-1) (siznow1) 为当前节点与子树上每个节点所构成的点对。
    • 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=ljnowljtosizto×(siznowsizto1)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=ansnowanstoljto
    • 故可知,加入 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=(siznowsizto)×(sizto1)+ljnownew+siznowsizto
      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+ansnowanstoljto+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);
       }
    }
    

小贴士:

  • 记得开 longlong,乘法会溢出。
  • 记得判断父亲节点,不然会 RE

时间复杂度分析

  • 每个点只在 dfs1dfs2 中分别出现一遍,故时间复杂度时线性 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);
	}
}


  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值