G - 聰聰可可 HYSBZ - 2152 (點分治)
聰聰和可可是兄弟倆,他們倆經常為了一些瑣事打起來,例如家中只剩下最後一根冰棍而兩人都想吃、兩個人都想玩兒電腦(可是他們家只有一臺電腦)……遇到這種問題,一般情況下石頭剪刀布就好了,可是他們已經玩兒膩了這種低智商的遊戲。他們的爸爸快被他們的爭吵煩死了,所以他發明了一個新遊戲:由爸爸在紙上畫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 【樣例說明】 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。
中文題,就不解釋題意了。只說一說需要注意的地方,兩人可以選擇相同的節點,而且選擇節點(1, 2)和(2, 1)是兩種不同的情況。
思路:
算是很裸的點分治,模板往上套就行,關鍵在於容斥計算答案的那一步,題目說是要統計路徑和為3的倍數的情況,直接對路徑長度取餘(對3取餘),得到的結果分別存到dist[0], dist[1], dist[2]中,最後統計這棵子樹的答案時直接就是dist[1] * dist[2] * 2 + dist[0] * dist[0]。
程式碼裡還有每一步的解釋:
#include <cmath> #include <iostream> #include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int maxn = 1e5 + 100; int top, Max, root; int head[maxn], size[maxn]; int dist[20]; bool vis[maxn];; int ans, n; struct node { int v, next; int w; }edge[maxn * 2]; inline void add(int u, int v, int w) { edge[top].v = v; edge[top].w = w; edge[top].next = head[u]; head[u] = top++; } inline void Init() { top = 0; memset(head, -1, sizeof(head)); memset(vis, false, sizeof(vis)); } //以上都是儲存樹的結構,下面進入正題 void dfs_size(int u, int father) { //求某顆子樹的節點資訊,是為了方便之後求重心 size[u] = 1; //把當前節點先統計上 for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(v != father && !vis[v]) { dfs_size(v, u); size[u] += size[v]; //回溯的時候把節點u的所有子節點都加上 } } } void dfs_root(int r, int u, int father) { //求樹的重心 int maxs = size[r] - size[u]; //maxs中存放以節點u為根時最大子樹的節點數量 for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(v != father && !vis[v]) { dfs_root(r, v, u); maxs = max(maxs, size[v]); } } if(maxs < Max) { //通過不斷的比較,得出重心 Max = maxs; root = u; } } void dfs_distance(int u, int father, int dis) { //求每個節點到該子樹的根節點的距離(對三取餘後) dist[dis % 3] ++; for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(v != father && !vis[v]) { dfs_distance(v, u, dis + edge[i].w); } } } int calc(int u, int dis) { //計算每顆子樹的結果 dist[0] = dist[1] = dist[2] = 0; dfs_distance(u, 0, dis); return dist[1] * dist[2] * 2 + dist[0] * dist[0]; } void divide(int u) { Max = n; dfs_size(u, 0); dfs_root(u, u, 0); //求樹的重心 ans += calc(root, 0); vis[root] = true; //求過的標記一下 for(int i = head[root]; i != -1; i = edge[i].next) { int v = edge[i].v; int w = edge[i].w; if(!vis[v]) { ans -= calc(v, w); //減去不合法的情況 divide(v); } } } int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } int main() { //freopen("in.txt", "r", stdin); cin >> n; int u, v; int w; Init(); for(int i = 1; i <= n - 1; ++ i) { scanf("%d%d%d", &u, &v, &w); add(u, v, w % 3); add(v, u, w % 3); } ans = 0; divide(1); int temp = gcd(n * n, ans); //求公因子,約分.. printf("%d/%d\n", ans / temp, n * n / temp); return 0; }