[BZOJ 2152]聪聪可可
Description
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
Input
输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。
Output
以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。
Sample Input
1 2 1
1 3 2
1 4 1
2 5 3
Sample Output
HINT
【样例说明】13组点对分别是(1,1) (2,2) (2,3) (2,5) (3,2) (3,3) (3,4) (3,5) (4,3) (4,4) (5,2) (5,3) (5,5)。
【数据规模】
对于100%的数据,n<=20000。
题解
解法一
考虑树形$DP$解法,我们令$f[u][0/1/2]$表示以$u$为根当前已处理过的子树中路径长度$mod$ $3$为$0/1/2$的条数。
对于点对的处理我们用类似->这道题<-的方法。
我们令$flag[0/1/2]$为处理$u$的某个子树时该子树中以$u$为根路径长度$mod$ $3$为$0/1/2$的条数。
此时我们让
ans += flag[0]*f[u][0] + flag[1]*f[u][2] + flag[2]*f[u][1];
此后,将$flag$累加到$f[u]$上。
最后记得再单独加上$f[u][0]$,表示$u$为路径端点的情况。
最后对于答案的处理记得$*2$(无序点对变有序点对),再加上$n$(未考虑$(i,i)$这样的情况)。
1 //It is made by Awson on 2017.9.20 2 #include <set> 3 #include <map> 4 #include <cmath> 5 #include <ctime> 6 #include <queue> 7 #include <stack> 8 #include <string> 9 #include <cstdio> 10 #include <vector> 11 #include <cstdlib> 12 #include <cstring> 13 #include <iostream> 14 #include <algorithm> 15 #define Min(a, b) ((a) < (b) ? (a) : (b)) 16 #define Max(a, b) ((a) > (b) ? (a) : (b)) 17 #define LL long long 18 using namespace std; 19 const int N = 20000; 20 const int INF = ~0u>>1; 21 22 int n, u, v, c; 23 struct tt { 24 int to, cost, next; 25 }edge[N*2+5]; 26 int path[N+5], top; 27 int f[N+5][3]; 28 int ans = 0; 29 30 int gcd(int a, int b) { 31 return b == 0 ? a : gcd(b, a%b); 32 } 33 void add(int u, int v, int c) { 34 edge[++top].to = v; 35 edge[top].cost = c; 36 edge[top].next = path[u]; 37 path[u] = top; 38 } 39 void dfs(int u, int fa) { 40 int flag[3]; 41 for (int i = path[u]; i; i = edge[i].next) 42 if (edge[i].to != fa) { 43 dfs(edge[i].to, u); 44 flag[edge[i].cost%3] = f[edge[i].to][0]; 45 flag[(1+edge[i].cost)%3] = f[edge[i].to][1]; 46 flag[(2+edge[i].cost)%3] = f[edge[i].to][2]; 47 ans += flag[0]*f[u][0] + flag[1]*f[u][2] + flag[2]*f[u][1]; 48 f[u][0] += flag[0]; 49 f[u][1] += flag[1]; 50 f[u][2] += flag[2]; 51 } 52 ans += f[u][0]; 53 f[u][0]++; 54 } 55 void work() { 56 for (int i = 1; i < n; i++) { 57 scanf("%d%d%d", &u, &v, &c); 58 c %= 3; 59 add(u, v, c); add(v, u, c); 60 } 61 dfs(1, 0); 62 int tmp = gcd(ans*2+n, n*n); 63 printf("%d/%d\n", (ans*2+n)/tmp, n*n/tmp); 64 } 65 int main() { 66 while (~scanf("%d", &n)) 67 work(); 68 return 0; 69 }
解法二
这道题正解是点分治...
同样还是之前的套路,我们找到重心后,每次只处理与重心有关的路径。
每次找到重心,统计以重心为根的子树中路径长度个数;
同样我们令$f[u][0/1/2]$表示以$u$为根路径长度$mod$ $3$为$0/1/2$的条数。
可以用之前的做法统计,但这里给出另一种思路。
我们先统计所有的子树中节点到根的路径长度个数,用刚刚的方程转移。
但直接相乘会出现某条路径不是简单路径,简而言之就是两个端点来自同一个子树,
其实我们只要将来自同一个子树的方案删去即可。
(因为两种解法一起写的,这里给出的点分治的代码受树形DP的代码思想影响比较大,其实很多程序段是不必要的)
1 //It is made by Awson on 2017.9.20 2 #include <set> 3 #include <map> 4 #include <cmath> 5 #include <ctime> 6 #include <queue> 7 #include <stack> 8 #include <string> 9 #include <cstdio> 10 #include <vector> 11 #include <cstdlib> 12 #include <cstring> 13 #include <iostream> 14 #include <algorithm> 15 #define Min(a, b) ((a) < (b) ? (a) : (b)) 16 #define Max(a, b) ((a) > (b) ? (a) : (b)) 17 #define LL long long 18 using namespace std; 19 const int N = 20000; 20 const int INF = ~0u>>1; 21 22 struct tt { 23 int to, cost, next; 24 }edge[N*2+5]; 25 int path[N+5], top; 26 int n, u, v, c; 27 int ans; 28 bool vis[N+5]; 29 int size[N+5], mx[N+5], minsize, root; 30 int f[N+5][3]; 31 32 int gcd(int a, int b) { 33 return b == 0 ? a : gcd(b, a%b); 34 } 35 void add(int u, int v, int c) { 36 edge[++top].to = v; 37 edge[top].cost = c; 38 edge[top].next = path[u]; 39 path[u] = top; 40 } 41 void get_size(int u, int fa) { 42 size[u] = 1; mx[u] = 0; 43 for (int i = path[u]; i; i = edge[i].next) 44 if (!vis[edge[i].to] && edge[i].to != fa) { 45 get_size(edge[i].to, u); 46 size[u] += size[edge[i].to]; 47 mx[u] = Max(mx[u], size[edge[i].to]); 48 } 49 } 50 void get_root(int r, int u, int fa) { 51 mx[u] = Max(mx[u], size[r]-size[u]); 52 if (mx[u] < minsize) minsize = mx[u], root = u; 53 for (int i = path[u]; i; i = edge[i].next) 54 if (!vis[edge[i].to] && edge[i].to != fa) 55 get_root(r, edge[i].to, u); 56 } 57 void get_ans(int u, int fa) { 58 f[u][0] = 1; 59 f[u][1] = f[u][2] = 0; 60 for (int i = path[u]; i; i = edge[i].next) 61 if (!vis[edge[i].to] && edge[i].to != fa) { 62 get_ans(edge[i].to, u); 63 f[u][edge[i].cost%3] += f[edge[i].to][0]; 64 f[u][(1+edge[i].cost)%3] += f[edge[i].to][1]; 65 f[u][(2+edge[i].cost)%3] += f[edge[i].to][2]; 66 } 67 } 68 void solve(int x) { 69 minsize = INF; 70 int flag[3] = {0}; 71 get_size(x, 0); 72 get_root(x, x, 0); 73 vis[root] = true; 74 f[root][0] = 1; 75 f[root][1] = f[root][2] = 0; 76 for (int i = path[root]; i; i = edge[i].next) 77 if (!vis[edge[i].to]) { 78 get_ans(edge[i].to, 0); 79 flag[edge[i].cost%3] = f[edge[i].to][0]; 80 flag[(1+edge[i].cost)%3] = f[edge[i].to][1]; 81 flag[(2+edge[i].cost)%3] = f[edge[i].to][2]; 82 ans -= flag[0]*flag[0] + flag[1]*flag[2]*2; 83 f[root][0] += flag[0]; 84 f[root][1] += flag[1]; 85 f[root][2] += flag[2]; 86 } 87 ans += f[root][0]*f[root][0] + f[root][1]*f[root][2]*2; 88 for (int i = path[root]; i; i = edge[i].next) 89 if (!vis[edge[i].to]) 90 solve(edge[i].to); 91 } 92 void work() { 93 for (int i = 1; i < n; i++) { 94 scanf("%d%d%d", &u, &v, &c); 95 c %= 3; 96 add(u, v, c); add(v, u, c); 97 } 98 ans = 0; 99 solve(1); 100 int tmp = gcd(ans, n*n); 101 printf("%d/%d\n", ans/tmp, n*n/tmp); 102 } 103 int main() { 104 while (~scanf("%d", &n)) 105 work(); 106 return 0; 107 }