标签:names 标记 lan style main c++ 差分 pre ret
题解
考虑一条路径 $(u,v)$ 的影响,那对 $lca$ 及其祖先的贡献都是 $(d_u-d_lca)(d_v-d_lca)$ ,对 $(u,v)$ 这条路径上的点,每个点和其子树内的贡献是一样的,列式子发现是等差数列的形式,于是我们可以做树上差分,具体来说我们发现对于 $(u,lca)$ 这条路径上的点 $x$ , $\Delta a_x-\Delta a_{son}=dis_{u,v}+1-2(dp_x-dp_u)$ ,于是我们可以在 $u$ 或 $v$ 上打上首项和公差的标记,在 $lca$ 上打上末项和公差的负标记,每次首项都加上公差即可
效率: $O(nlogn)$ (需要倍增)
代码
#include <bits/stdc++.h> using namespace std; const int N=3e5+5,M=N<<1; int n,m,hd[N],V[M],nx[M],t,dp[N],fa[N][20]; long long a[N],b[N],s[N]; void add(int u,int v){ nx[++t]=hd[u];V[hd[u]=t]=v; } void dfs1(int u,int fr){ dp[u]=dp[fa[u][0]=fr]+1; for (int i=1;fa[fa[u][i-1]][i-1];i++) fa[u][i]=fa[fa[u][i-1]][i-1]; for (int i=hd[u];i;i=nx[i]) if (V[i]!=fr) dfs1(V[i],u); } int lca(int u,int v){ if (dp[u]<dp[v]) swap(u,v); for (int i=19;~i;i--) if (dp[fa[u][i]]>=dp[v]) u=fa[u][i]; if (u==v) return u; for (int i=19;~i;i--) if (fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i]; return fa[u][0]; } void dfs2(int u,int fr){ for (int v,i=hd[u];i;i=nx[i]) if ((v=V[i])!=fr) dfs2(v,u),a[u]+=a[v],b[u]+=b[v]; a[u]-=b[u]; } void dfs3(int u,int fr){ for (int v,i=hd[u];i;i=nx[i]) if ((v=V[i])!=fr) s[v]=s[u]-a[v],dfs3(v,u); } int main(){ cin>>n>>m; for (int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v), add(u,v),add(v,u); dfs1(1,0); for (int u,v,z,l;m--;){ scanf("%d%d",&u,&v); z=lca(u,v);l=dp[u]+dp[v]-(dp[z]<<1); a[u]+=l+1;a[v]+=l+1;a[z]-=2; b[u]+=2;b[v]+=2;b[z]-=4; s[1]+=1ll*(dp[u]-dp[z])*(dp[v]-dp[z]); } dfs2(1,0);dfs3(1,0); for (int i=1;i<=n;i++) printf("%lld\n",s[i]); return 0; }
标签:names 标记 lan style main c++ 差分 pre ret
原文地址:https://www.cnblogs.com/xjqxjq/p/12283450.html