聪 聪 可 可 聪聪可可 聪聪可可
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。
他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。
聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
n < = 20000 n<=20000 n<=20000.
最 初 想 法 \color{blue}{最初想法} 最初想法
点分治? 不会, 打暴力跳过…
正 解 部 分 \color{red}{正解部分} 正解部分
方 法 1 : 方法 1: 方法1: 树形dp
设
F
[
i
,
j
]
F[i, j]
F[i,j] 表示一个端点为
i
i
i 点, 含有多少边权和
%
3
=
j
\%3=j
%3=j的路径,
因为自己到自己为一条路径, 所以初值
F
[
i
,
0
]
=
1
F[i,0]=1
F[i,0]=1, 利用子树进行转移, 具体地说:
从头到尾扫所有子树, 设扫到中间时, 已有的路径数为 F [ i , j ] F[i, j] F[i,j], 当前子树的路径数为 F [ t o , j ] F[to, j] F[to,j],
- 更新答案: A n s + = 2 ∗ F [ i , j ] ∗ F [ t o , 3 − j ] Ans\ +=2* F[i, j]*F[to,3-j] Ans +=2∗F[i,j]∗F[to,3−j]
- 更新 d p dp dp数组: F [ i , j ] + = F [ t o , ( ( j − e d g e [ p ] . w ) % 3 + 3 ) % 3 ) ] F[i, j]\ += F[to, ((j-edge[p].w)\%3+3)\%3)] F[i,j] +=F[to,((j−edge[p].w)%3+3)%3)] .
两个端点都为自己的路径 在上述累加方式中没有涉及到, 所以最后答案要加上 N N N .
方 法 2 : 方法 2: 方法2: 点分治
待 填 坑 . 待填坑. 待填坑.
实 现 部 分 \color{red}{实现部分} 实现部分
- 注意最后答案要加上 N N N .
- 注意统计答案时要乘 2 2 2 .
- 在处理模意义下的负数时不能够直接取绝对值 .
#include<bits/stdc++.h>
#define reg register
const int maxn = 20004;
int N;
int Ans;
int num0;
int head[maxn];
int F[maxn][3];
struct Edge{ int nxt, to, w; } edge[maxn<<1];
void Add(int from, int to, int w){
edge[++ num0] = (Edge){ head[from], to, w };
head[from] = num0;
}
int Mod(int x){ return (x%3+3) % 3; }
int Gcd(int a, int b){ return !b?a:Gcd(b, a%b); }
void DFS(int k, int fa){
F[k][0] = 1;
for(reg int i = head[k]; i; i = edge[i].nxt){
int to = edge[i].to;
if(to == fa) continue ;
DFS(to, k);
for(reg int j = 0; j < 3; j ++) Ans += 2*F[k][j]*F[to][Mod(-j-edge[i].w)];
for(reg int j = 0; j < 3; j ++) F[k][j] += F[to][Mod(j-edge[i].w)];
}
}
int main(){
scanf("%d", &N);
for(reg int i = 1; i < N; i ++){
int x, y, w;
scanf("%d%d%d", &x, &y, &w); w %= 3;
Add(x, y, w), Add(y, x, w);
}
DFS(1, 0);
Ans += N; //!
int tot = N*N;
int gcd = Gcd(tot, Ans);
Ans /= gcd, tot /= gcd;
printf("%d/%d\n", Ans, tot);
return 0;
}