P2634 [國家集訓隊]聰聰可可
題面
題目描述
聰聰和可可是兄弟倆,他們倆經常為了一些瑣事打起來,例如家中只剩下最後一根冰棍而兩人都想吃、兩個人都想玩兒電腦(可是他們家只有一臺電腦)……
遇到這種問題,一般情況下石頭剪刀布就好了,可是他們已經玩兒膩了這種低智商的遊戲。
他們的爸爸快被他們的爭吵煩死了,所以他發明了一個新遊戲:由爸爸在紙上畫 \(n\) 個“點”,並用 \(n−1\) 條“邊”把這 \(n\) 個“點”恰好連通
(其實這就是一棵樹)。並且每條“邊”上都有一個數。接下來由聰聰和可可分別隨即選一個點(當然他們選點時是看不到這棵樹的),
如果兩個點之間所有邊上數的和加起來恰好是 \(3\) 的倍數,則判聰聰贏,否則可可贏。
聰聰非常愛思考問題,在每次遊戲後都會仔細研究這棵樹,希望知道對於這張圖自己的獲勝概率是多少。
現請你幫忙求出這個值以驗證聰聰的答案是否正確。
輸入格式
輸入的第 \(1\) 行包含 \(1\) 個正整數 \(n\)。後面 \(n−1\) 行,每行 \(3\) 個整數 \(x\),\(y\),\(w\),表示 \(x\) 號點和 \(y\) 號點之間有一條邊,上面的數是 \(w\)。
輸出格式
以即約分數形式輸出這個概率(即 a/b 的形式,其中 \(a\) 和 \(b\) 必須互質。如果概率為 \(1\),輸出 \(1/1\))。
輸入輸出樣例
輸入 #1
5
1 2 1
1 3 2
1 4 1
2 5 3
輸出 #1
13/25
說明/提示
【樣例說明】
131313 組點對分別是(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≤2×10^4
題解
這幾天剛學會點分治,所以拿道題練練手。
好像這個題除了用點分治還可以用動態規劃來解決。
還是按套路,找出整棵樹的重心,然後分治解決每個子樹中的答案。
我們主要看 \(calc\) 函式的實現。
題目讓求的是路徑長度是三的倍數。
我們可以記錄一下每條路徑 模以 3 之後的結果。
設 \(tong[i]\) 表示餘數為 \(i\)
首先一條 餘數為2的路徑和一條餘數為1 路徑的可以配成一條合法路徑,也就是 \(tong[2] \times tong[1] \times 2\)
乘 2 是因為 (x,y) 和 (y,x) 算兩個點對
兩條餘數為0的路徑 也可以配成一條合法路徑 即 \(tong[0] \times tong[0]\)
然後,我們這個點的答案就是 \(tong[2] \times tong[1] \times 2 + tong[0] \times tong[0]\)
calc 函式
int calc(int x,int d)
{
tong[1] = tong[2] = tong[0] = 0;//每次計算都要清空一下
dis[x] = d; cnt = 0;
a[++cnt] = dis[x];
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(vis[to]) continue;
dis[to] = dis[x] + e[i].w;
get_dis(to,x);
}
for(int i = 1; i <= cnt; i++)
{
tong[a[i]%3]++;//計算一下路徑長度模以三的餘數
}
return tong[1] * tong[2] * 2 + tong[0] * tong[0];
}
總體程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e4+10;
int n,m,sum_siz,root,tot,cnt,ans,u,v,w;
int head[N],max_siz[N],siz[N],dis[N],a[N],tong[10];
bool vis[N];
struct node
{
int to,net,w;
}e[N<<1];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void add(int x,int y,int w)
{
e[++tot].w = w;
e[tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
int gcd(int a,int b)
{
if(b == 0) return a;
else return gcd(b,a%b);
}
void get_root(int x,int fa)//找重心
{
max_siz[x] = 0; siz[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa || vis[to]) continue;
get_root(to,x);
siz[x] += siz[to];
max_siz[x] = max(max_siz[x],siz[to]);
}
max_siz[x] = max(max_siz[x],sum_siz-siz[x]);
if(max_siz[x] < max_siz[root]) root = x;
}
void get_dis(int x,int fa)//找距離
{
a[++cnt] = dis[x];
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa || vis[to]) continue;
dis[to] = dis[x] + e[i].w;
get_dis(to,x);
}
}
int calc(int x,int d)//統計在 x 點的答案
{
tong[1] = tong[2] = tong[0] = 0;
dis[x] = d; cnt = 0;
a[++cnt] = dis[x];
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(vis[to]) continue;
dis[to] = dis[x] + e[i].w;
get_dis(to,x);
}
for(int i = 1; i <= cnt; i++)
{
tong[a[i]%3]++;
}
return tong[1] * tong[2] * 2 + tong[0] * tong[0];
}
void slove(int x)//點分治
{
ans += calc(x,0);
vis[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(vis[to]) continue;
ans -= calc(to,e[i].w);
max_siz[0] = n; sum_siz = siz[to]; root = 0;
get_root(to,0); slove(root);
}
}
int main()
{
n = read();
for(int i = 1; i <= n-1; i++)
{
u = read(); v = read(); w = read();
add(u,v,w); add(v,u,w);
}
max_siz[0] = sum_siz = n; root = 0;
get_root(1,0); slove(root);//找整棵樹的重心
int d = gcd(ans,n*n);//約分
printf("%d/%d",ans/d,(n*n)/d);
return 0;
}