1. 程式人生 > 實用技巧 >P2634 [國家集訓隊]聰聰可可

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