传送门:bzoj2152
题意
在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点,如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。求对于这张图聪聪的获胜概率是多少。
数据范围
对于100%的数据,n<=20000。
题解
点分治板题,模一下就好了。
代码
#include<bits/stdc++.h>
const int INF=0x7fffffff;
using namespace std;
typedef long long ll;
const int N=2e4+10;
ll qw,sum[4],sim[N],mx[N],ans,S;
int n,x,y,mod=3,tot;
int head[N],to[N<<1],nxt[N<<1],w[N<<1];
int root,MX;
bool vis[N];
inline void lk(int u,int v,int val){
to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=val;
}
inline void getroot(int now,int f)
{
sim[now]=1;mx[now]=0;
for(int i=head[now];i;i=nxt[i]){
int e=to[i];
if(vis[e]||(e==f)) continue;
getroot(e,now);
sim[now]+=sim[e];
mx[now]=max(mx[now],sim[e]);
}
mx[now]=max(mx[now],S-sim[now]);
if(mx[now]<MX){root=now;MX=mx[now];}
}
inline void query(int now,int f,int val)
{
sum[val]++;
for(int i=head[now];i;i=nxt[i])
{
int e=to[i];
if(vis[e]||(e==f)) continue;
query(e,now,(val+w[i])%mod);
}
}
inline ll solve(int now,int val)
{
sum[0]=sum[1]=sum[2]=0;
query(now,0,val);
return (sum[0]*sum[0]+2*sum[1]*sum[2]);
}
inline void divide(int now)
{
ans+=solve(now,0);
vis[now]=true;
for(int i=head[now];i;i=nxt[i]){
int e=to[i];
if(vis[e]) continue;
ans-=solve(e,w[i]);
MX=INF;root=0;S=sim[e];
getroot(e,0);
divide(root);
}
}
inline ll gcd(ll a,ll b)
{
return (b==0)? a:gcd(b,a%b);
}
int main(){
memset(vis,false,sizeof(vis));
scanf("%d",&n);
for(int c,i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&c);
c%=mod;
lk(x,y,c);lk(y,x,c);
}
S=n;MX=INF;
getroot(1,0);
divide(root);
qw=n*n;ll k=gcd(ans,qw);
printf("%lld/%lld\n",ans/k,qw/k);
return 0;
}