【BZOJ】聪聪可可-点分治

传送门: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;
}

阅读更多
版权声明:侵删,转载请附带链接或评论 https://blog.csdn.net/corsica6/article/details/79952347
个人分类: 算法
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭