Solution
我抄我自己
对于正在处理的以 u 为根的树,将子树内路径分成两种
- 经过 u 点
- 不经过 u 点
对于第一类,依次遍历根的每个儿子为根的子树,计算出子树内每个点到原根的路径长度,
每遍历完一个儿子为根的子树,计算它以内的点与在它之前遍历的子树内的点组成的路径长度%3=0的点对个数
(详见代码)
对于第二类,处理去掉 u 后分成的每棵树(以这棵树的重心为根)
Code
#include <cstdio> #include <cstdlib> #include <algorithm> using namespace std; const int N=2e4+10; int ans[3],cur[3],tot,head[N],nxt[N*2],ver[N*2],edge[N*2]; int fz,fm,n,u,v,w,rt,sum,d[N],dis[N],si[N]; bool vis[N]; void add(int u,int v,int w) { ver[++tot]=v,nxt[tot]=head[u],edge[tot]=w,head[u]=tot; } void getrt(int u,int fa) { d[u]=0,si[u]=1; for(int i=head[u];i;i=nxt[i]) { int v=ver[i]; if(v==fa || vis[v]) continue; getrt(v,u); si[u]+=si[v]; d[u]=max(d[u],si[v]); } d[u]=max(d[u],sum-si[u]); if(d[u]<=d[rt]) rt=u; } void getdis(int u,int fa) { ans[dis[u]%3]++; for(int i=head[u];i;i=nxt[i]) { int v=ver[i]; if(v==fa || vis[v]) continue; dis[v]=dis[u]+edge[i]; getdis(v,u); } } void calc(int u) { cur[0]=cur[1]=cur[2]=0; cur[0]=1; for(int i=head[u];i;i=nxt[i]) { int v=ver[i]; if(vis[v]) continue; dis[v]=edge[i]; ans[0]=ans[1]=ans[2]=0; getdis(v,u); fz+=cur[0]*ans[0]+cur[1]*ans[2]+cur[2]*ans[1]; cur[0]+=ans[0],cur[1]+=ans[1],cur[2]+=ans[2]; } } void dfs(int u) { vis[u]=true,calc(u); for(int i=head[u];i;i=nxt[i]) { int v=ver[i]; if(vis[v]) continue; d[rt=0]=sum=si[v]; getrt(v,0),dfs(rt); } } int gcd(int a,int b) { if(a==0 || b==0) return a+b; return gcd(b,a%b); } int main() { scanf("%d",&n); for(int i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&w); add(u,v,w),add(v,u,w); } d[rt=0]=sum=n; getrt(1,0); dfs(1); fz=fz*2+n,fm=n*n; int x=gcd(fz,fm); printf("%d/%d",fz/x,fm/x); return 0; }