P2634 [国家集训队]聪聪可可

题面

Link

题目描述

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……

遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。

他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画 \(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;
}

posted @ 2020-09-02 20:47  genshy  阅读(155)  评论(0编辑  收藏  举报