【P2634】聪聪可可——点分治

(题面来自Luogu)

题目描述

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

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

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

输入输出格式

输入格式:

输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。

输出格式:

以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。

 

  用点分治统计从每个点u出发的路径,在cnt[k]中计数;其中k属于{0, 1, 2},表示该数组统计通过当前节点的长度%3等于k的路径数目。考虑将这些节点两两组合成长度%3等于0的路径:cnt[1]可以与cnt[2]两两组合,且题中给定的是有序点对,那么通过点u的满足条件的路径数ret += (cnt[2] * cnt[1] * 2)。cnt[0]可以两两组合,且可以重复,则ret += cnt[0] * cnt[0]。

代码:

 
  1. #include <iostream>    
  2. #include <cstdio>    
  3. #include <cstring>    
  4. #include <cctype>    
  5. #define BUG puts("$$$")    
  6. #define maxn 20010    
  7. using namespace std;    
  8. const int inf = 0x3f3f3f3f;    
  9. int n;    
  10. template <typename T>    
  11. void read(T &x) {    
  12.     x = 0;    
  13.     char ch = getchar();    
  14.     int f = 1;    
  15.     while (!isdigit(ch)) {    
  16.         if (ch == '-') f = -1;     
  17.         ch = getchar();    
  18.     }    
  19.     while (isdigit(ch)) {    
  20.         x = x * 10 + (ch ^ 48);    
  21.         ch = getchar();    
  22.     }    
  23.     x *= f;    
  24. }    
  25. long long gcd(long long a, long long b) {    
  26.     if (!b) return a;    
  27.     return gcd(b, a % b);    
  28. }    
  29. int head[maxn], top;    
  30. struct E {    
  31.     int to, nxt, w;    
  32. } edge[maxn << 1];    
  33. inline void insert(int u, int v, int w) {    
  34.     edge[++top] = (E) {v, head[u], w};    
  35.     head[u] = top;    
  36. }    
  37. int cnt[3];    
  38. int size[maxn], Size, mn, root;    
  39. bool vis[maxn];    
  40. void find_rt(int u, int pre) {    
  41.     size[u] = 1;    
  42.     int son = 0;    
  43.     for (int i = head[u]; i; i = edge[i].nxt) {    
  44.         int v = edge[i].to;    
  45.         if (v == pre || vis[v]) continue;    
  46.         find_rt(v, u);    
  47.         size[u] += size[v];    
  48.         if (size[v] > size[son]) son = v;    
  49.     }    
  50.     int cur = max(size[son], Size - size[u]);    
  51.     if (cur < mn)    
  52.         mn = cur, root = u;    
  53. }    
  54. void dfs(int u, int pre, int depth, int val) {    
  55.     cnt[depth % 3] += val;    
  56.     for (int i = head[u]; i; i = edge[i].nxt) {    
  57.         int v = edge[i].to;    
  58.         if (v == pre || vis[v]) continue;    
  59.         dfs(v, u, depth + edge[i].w, val);    
  60.     }    
  61. }    
  62. long long solve(int u, int extra) {    
  63.     dfs(u, 0, extra, 1);    
  64.     long long ret = 1LL * cnt[1] * cnt[2] * 2 + 1LL * cnt[0] * cnt[0];    
  65.     dfs(u, 0, extra, -1);    
  66.     return ret;    
  67. }    
  68. long long ans;    
  69. void divide(int u) {    
  70.     vis[u] = true;    
  71.     ans += solve(u, 0);    
  72.     int curSize = Size;    
  73.     for (int i = head[u]; i; i = edge[i].nxt) {    
  74.         int v = edge[i].to;    
  75.         if (vis[v]) continue;    
  76.         ans -= solve(v, edge[i].w);    
  77.         mn = inf, Size = size[u] > size[v] ? size[v] : curSize - size[u];    
  78.         find_rt(v, u);    
  79.         divide(root);    
  80.     }    
  81. }    
  82. int main() {    
  83.     read(n);    
  84.     int u, v, w;    
  85.     for (int i = 1; i < n; ++i) {    
  86.         read(u), read(v), read(w);    
  87.         insert(u, v, w), insert(v, u, w);    
  88.     }    
  89.     mn = inf, Size = n;    
  90.     find_rt(1, 0);    
  91.     divide(root);    
  92.     long long down = 1LL * n * n, k = gcd(down, ans);    
  93.     printf("%lld/%lld", ans / k, down / k);    
  94.     return 0;    
  95. }    
posted @ 2019-08-14 21:35  onyYuan  阅读(122)  评论(0编辑  收藏  举报