1. 程式人生 > >[BZOJ 2152]聰聰可可

[BZOJ 2152]聰聰可可

true pla clas navi 分治 www 臺電腦 close 接下來

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

5
1 2 1
1 3 2
1 4 1
2 5 3

Sample Output

13/25

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 }
樹形DP

解法二

這道題正解是點分治...

同樣還是之前的套路,我們找到重心後,每次只處理與重心有關的路徑。

每次找到重心,統計以重心為根的子樹中路徑長度個數;

同樣我們令$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 }
點分治

[BZOJ 2152]聰聰可可