题目大意:
给你一棵树,假如树上两点间的距离是 3 的倍数 的点对有 s 对,则输出最简分数 s/n ,其中 n 表示所有整棵树的点对总数。
分析:
1、显然,可以采用点分治。
2、当然考虑到数据过大,点分治中求距离时,可以不需要把真实距离依次存入 dis[] 数组中。可以将每个距离值 %3 ,这样如果有两个距离 x y ,若使 x + y 为 3 的倍数,只需要满足两点:
- x%3==0 && y%3==0
- x%3==1 && y%3==2
上述 x y 可交换。故只需要用 vis[0] 、vis[1] 、 vis[2] 来标记距离,然后求和即可。
代码如下:
#include<iostream> #include<algorithm> #include<string.h> #define maxn 20008 #define inf 0x3f3f3f3f using namespace std; bool vis[maxn]; int n,cnt,root,size,ans; int head[maxn],sz[maxn],f[maxn],flag[4]; struct Edge{ int to; int val; int next; }edge[maxn<<1]; inline void add(int u,int v,int w){ edge[++cnt].to=v; edge[cnt].val=w; edge[cnt].next=head[u]; head[u]=cnt; return; } int gcd(int a,int b){ if(!b) return a; return gcd(b,a%b); } void getdis(int u,int pre,int d){ for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(v==pre||vis[v]) continue; flag[(d+edge[i].val)%3]++; getdis(v,u,d+edge[i].val); } return; } void calc(int u,int d,int s){ flag[0]=flag[1]=flag[2]=0; flag[d%3]++; getdis(u,-1,d); ans+=s*flag[0]*(flag[0]-1); ans+=s*flag[1]*flag[2]*2; return; } void getroot(int u,int pre){ sz[u]=1,f[u]=0; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(v==pre||vis[v]) continue; getroot(v,u); sz[u]+=sz[v]; f[u]=max(f[u],sz[v]); } f[u]=max(f[u],size-sz[u]); if(f[u]<f[root]) root=u; return; } void divide(int u){ vis[u]=true; calc(u,0,1); int sum=size; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(vis[v]) continue; calc(v,edge[i].val,-1); size=sz[v]>sz[u]?sum-sz[u]:sz[v]; root=0; getroot(v,-1); divide(root); } return; } int main() { scanf("%d",&n); int x,y,w; for(int i=1;i<n;i++){ scanf("%d%d%d",&x,&y,&w); add(x,y,w),add(y,x,w); } root=0; f[root]=inf; size=n; getroot(1,-1); divide(root); ans+=n; int t=n*n; int g=gcd(ans,t); ans/=g,t/=g; printf("%d/%d ",ans,t); }