题目:洛谷P2634、BZOJ2152。
题目大意:有n个点,每个点有一个权值。现在两个人分别选两个点,如果两点的最短路在3进制下个位相等,则规定获胜。求总胜率(获胜选法/所有可能选法),两个人选的不同点调换算两种方案。
解题思路:点分治,每次找出树的重心,然后以重心为根,递归处理。
每次统计模3余0 1 2的个数,然后计算答案即可。
注意要去掉一些走非最短路径的(即重复)。
时间复杂度$O(n\log_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\n",ans/gcd,n*n/gcd));
}
}_