[树形dp] Codeforces 629E Famil Door and Roads
题目大意
给出一棵 \(n\) 个节点的树。有 \(m\) 个询问,每一个询问包含两个数 \(a,b\)。
我们可以对任意两个不相连的点连一条无向边,并且使得加上这条边后 \(a,b\) 处在一个环内。
对于每一个询问,求这样的环的期望长度。
\(2\leq n,m\leq 10^5\)
题解
树形dp+二次扫描换根。
设询问 \(u,v\) 这两个点,\(u\sim v\) 的这条树链把树分为三部分:
- 从 \(u\) 出发,不经过 \(u\sim v\) 这条树链,所能到达的点集,记为 \(A\)。
- 从 \(v\) 出发,不经过 \(u\sim v\) 这条树链,所能到达的点集,记为 \(B\)。
- 剩下的点构成的点集,记为 \(C\)。
记 \(Sum\) 表示新增一条跨越 \(u\sim v\) 这条链的边构成的所有环的长度之和,计 \(Num\) 表示新增一条跨越 \(u\sim v\) 这条链的边的方案数。则 \(Ans=\frac{Sum}{Num}\)。
\[Sum=\sum_{a\in A}\sum_{b\in B}(dis(a,u)+dis(v,b)+dis(u,v)+1)\\
=(dis(u,v)+1)\times|A||B|+\sum_{a\in A}\sum_{b\in B}(dis(a,u)+dis(v,b))\\
=(dis(u,v)+1)\times|A||B|+\sum_{a\in A}\sum_{b\in B}dis(a,u)+\sum_{a\in A}\sum_{b\in B}dis(v,b))\\
=(dis(u,v)+1)\times|A||B|+|B|\times\sum_{a\in A} dis(a,u)+|A|\times\sum_{b\in B} dis(v,b)\\
\]
\[Num=|A|\times|B|
\]
不妨以\(1\)为根,令 \(Deep[u]<Deep[v]\),令
\[dp[u][0]=\sum_{v\in subtree(u)} dis(u,v)\\
dp[u][1]=\sum_{v\notin subtree(u)} dis(u,v)
\]
即 \(dp[u][0]\) 表示从 \(u\) 出发向下走的所有路径的长度和,\(dp[u][1]\) 表示从 \(u\) 出发向上走的所有路径的长度和,设 \(fa\) 是 \(u\) 的父亲,那么有
\[dp[u][0]=\sum_{v\in son(u)}dp[v][0]+Size[v] \\
dp[u][1]=dp[fa][1]+dp[fa][0]-dp[u][0]-Size[u]+N-Size[u]
\]
我们可以先通过一次DFS求得 \(dp[u][0]\),再通过一次DFS扫描换根求得 \(dp[u][1]\),然后就容易求得 \(Sum\)。
对于 \(lca(u,v)=u\) 和 \(lca(u,v)\neq u\),稍有不同,需要分类讨论。
时间复杂度 \(O(n\log n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define RG register int
#define LL long long
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
struct Graph{
struct edge{int Next,to;};
edge G[200010];
int head[100010];
int cnt;
Graph():cnt(2){}
void clear(int node_num=0){
cnt=2;
if(node_num==0) memset(head,0,sizeof(head));
else fill(head,head+node_num+5,0);
}
void add_edge(int u,int v){
G[cnt].to=v;
G[cnt].Next=head[u];
head[u]=cnt++;
}
};
Graph G;
int Size[100010],Deep[100010],Anc[100010][18];
LL dp[100010][2];
int N,M;
void DFS(int u,int fa){
Size[u]=1;
Anc[u][0]=fa;
for(int i=1;i<18;++i)
Anc[u][i]=Anc[Anc[u][i-1]][i-1];
for(int i=G.head[u];i;i=G.G[i].Next){
int v=G.G[i].to;
if(v==fa) continue;
Deep[v]=Deep[u]+1;
DFS(v,u);
Size[u]+=Size[v];
dp[u][0]+=dp[v][0]+Size[v];
}
return;
}
void DFSB(int u,int fa){
if(fa) dp[u][1]=dp[fa][1]+dp[fa][0]-dp[u][0]-Size[u]+N-Size[u];
for(int i=G.head[u];i;i=G.G[i].Next){
int v=G.G[i].to;
if(v==fa) continue;
DFSB(v,u);
}
return;
}
int LCA(int u,int v){
int Root=1;
if(u==Root || v==Root) return Root;
if(Deep[u]<Deep[v]) swap(u,v);
for(RG i=17;i>=0;--i){
if(Deep[Anc[u][i]]>=Deep[v])
u=Anc[u][i];
}
if(u==v) return u;
for(RG i=17;i>=0;--i){
if(Anc[u][i]!=Anc[v][i]){
u=Anc[u][i];
v=Anc[v][i];
}
}
return Anc[u][0];
}
int main(){
Read(N);Read(M);
for(RG i=1;i<=N-1;++i){
int u,v;
Read(u);Read(v);
G.add_edge(u,v);
G.add_edge(v,u);
}
Deep[1]=1;
DFS(1,0);
DFSB(1,0);
for(RG i=1;i<=M;++i){
int u,v;
Read(u);Read(v);
if(Deep[u]>Deep[v]) swap(u,v);
int c=LCA(u,v);
if(c==u){
int s=v;
for(RG j=17;j>=0;--j)
if(Deep[Anc[s][j]]>=Deep[u]+1)
s=Anc[s][j];
LL temp=dp[u][1]+dp[u][0]-dp[s][0]-Size[s];
LL sum=(1LL+Deep[v]-Deep[u])*(LL)Size[v]*(LL)(N-Size[s])+(LL)Size[v]*temp+(LL)(N-Size[s])*dp[v][0];
double Ans=(double)sum/((double)Size[v]*(double)(N-Size[s]));
printf("%.8lf\n",Ans);
}else{
LL sum=(1LL+Deep[v]+Deep[u]-2*Deep[c])*(LL)Size[v]*(LL)Size[u]+
(LL)Size[v]*dp[u][0]+(LL)Size[u]*dp[v][0];
double Ans=(double)sum/((double)Size[v]*(double)Size[u]);
printf("%.8lf\n",Ans);
}
}
return 0;
}