聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。
他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画 n 个“点”,并用n−1 条“边”把这 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~