题目:洛谷P2634、BZOJ2152。
题目大意:有n个点,每个点有一个权值。现在两个人分别选两个点,如果两点的最短路在3进制下个位相等,则规定获胜。求总胜率(获胜选法/所有可能选法),两个人选的不同点调换算两种方案。
解题思路:点分治,每次找出树的重心,然后以重心为根,递归处理。
每次统计模3余0 1 2的个数,然后计算答案即可。
注意要去掉一些走非最短路径的(即重复)。
时间复杂度$O(nlog_2 n)$。
C++ Code:
#include<bits/stdc++.h> #define _ __;i nt m ain( ){} inline int max(int a,int b){return a>b?a:b;} inline int debian(){ int c=getchar(); for(;!isdigit(c);c=getchar()); int d=0; for(;isdigit(c);c=getchar()) d=(d<<3)+(d<<1)+(c^'0'); return d; } int __gcd(int a,int b){return b?__gcd(b,a%b):a;} static class Main{ private: int n,cnt,head[20005],rt,sz,f[20005],son[20005],ct[3],d[20005],ans; bool ur[20005]; struct edge{ int to,nxt,dis; }e[40005]; void getdp(int now,int pre){ ++ct[d[now]]; for(int i=head[now];i;i=e[i].nxt) if(e[i].to!=pre&&!ur[e[i].to]){ d[e[i].to]=(d[now]+e[i].dis)%3; getdp(e[i].to,now); } } int calc(int now,int v){ ct[0]=ct[1]=ct[2]=0; d[now]=v; getdp(now,0); return ct[0]*ct[0]+((ct[1]*ct[2])<<1); } void getrt(int now,int pre){ son[now]=1; f[now]=0; for(int i=head[now];i;i=e[i].nxt) if(!ur[e[i].to]&&e[i].to!=pre){ getrt(e[i].to,now); son[now]+=son[e[i].to]; f[now]=max(f[now],son[e[i].to]); } f[now]=max(f[now],sz-son[now]); if(f[now]<f[rt])rt=now; } void dfz(int now){ ur[now]=true; ans+=calc(now,0); for(int i=head[now];i;i=e[i].nxt) if(!ur[e[i].to]){ ans-=calc(e[i].to,e[i].dis); rt=0; sz=son[e[i].to]; getrt(e[i].to,0); dfz(rt); } } public: Main():n(debian()),cnt(0),rt(0),ans(0){ memset(head,0,sizeof head); for(int i=1;i<n;++i){ int x=debian(),y=debian(),z=debian()%3; e[++cnt]=(edge){y,head[x],z}; head[x]=cnt; e[++cnt]=(edge){x,head[y],z}; head[y]=cnt; } sz=f[0]=n; memset(ur,0,sizeof ur); getrt(1,0); dfz(rt); int gcd=__gcd(ans,n*n); exit(!printf("%d/%d ",ans/gcd,n*n/gcd)); } }_