题面
题目描述
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……
遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。
他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画 (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] imes tong[1] imes 2)
乘 2 是因为 (x,y) 和 (y,x) 算两个点对
两条余数为0的路径 也可以配成一条合法路径 即 (tong[0] imes tong[0])
然后,我们这个点的答案就是 (tong[2] imes tong[1] imes 2 + tong[0] imes 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;
}