题干

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。

他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画 n 个“点”,并用n1 条“边”把这 n 个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是 3的倍数,则判聪聪赢,否则可可赢。

聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

思路

考虑dp[i][j],i为当前的节点,j为0,1,2中的一个数

dp[i][j]表示以i为根节点的子树中,距离i节点的距离模3为j的个数。

于是我们只需要在遍历每一个点时对这个数组进行维护即可。

对于一条边(u,v)

则满足条件的个数要加上dp[v][i]*dp[u][mod(-i-边的长度)] ,i∈{0,1,2}   这里的(-i-边的长度)就是为了与i凑一个3出来

mod函数为:

int mod(int x){
    return (x%3+3)%3;
}

就是除以三取余数

于是就有了以下代码:

void add(int u,int v,int len){
    cnt++;
    to[cnt] = v;
    l[cnt] = len;
    nxt[cnt] = head[u];
    head[u] = cnt;
}

int mod(int x){
    return (x%3+3)%3;
}
int gcd(int x,int y){
    if(x%y==0) return y;
    return gcd(y,x%y);
}

void dfs(int u,int v){
    f[u][0]=1;
    for(int p=head[u];p;p=nxt[p]){
        int vv=to[p];
        if(vv==v)continue;
        dfs(vv,u);
        for(int i=0;i<=2;i++)
            fenzi+=f[vv][i]*f[u][mod(-i-l[p])]*2;
        for(int i=0;i<=2;i++)
            f[u][mod(i+l[p])]+=f[vv][i];
    }
}

int main(){
    ios::sync_with_stdio(false); 
    cin>>n;
    for(int i=1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs(1,-1);
    fenzi+=n;
    fenmu=n*n;
    g=gcd(fenzi,fenmu);
    fenzi/=g;
    fenmu/=g;
    cout<<fenzi<<"/"<<fenmu<<endl;

我们着重再看看这部分:

for(int i=0;i<=2;i++) fenzi+=f[vv][i]*f[u][mod(-i-l[p])]*2;

for(int i=0;i<=2;i++) f[u][mod(i+l[p])]+=f[vv][i];

第一个循环是求答案总数,而因为是有序数对所以要乘二。

而我们每次对一颗子树进行处理的时候,f[u]只更新了在他前面的子树,所以不会存在有多更新的情况

也就是说不会存在两条边在同一子树下的情况

最后要考虑只有一个点的情况也满足题意,所以fenzi要+n

AC~