1. 程式人生 > >G - 聰聰可可 HYSBZ - 2152 (點分治)

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;
}