【BZOJ 2152】聪聪可可

【题目】

传送门

Description

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。

他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画 n n n 个“点”,并用 n − 1 n-1 n1 条“边”把这 n n n 个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是 3 3 3 的倍数,则判聪聪赢,否则可可赢。

聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

Input

输入的第 1 1 1 行包含 1 1 1 个正整数 n n n。后面 n − 1 n-1 n1 行,每行 3 3 3 个整数 x x x y y y w w w,表示 x x x 号点和 y y y 号点之间有一条边,上面的数是 w w w

Output

以即约分数形式输出这个概率(即 “ a / b ” “a/b” a/b 的形式,其中 a a a b b b 必须互质。如果概率为 1 1 1,输出 “ 1 / 1 ” “1/1” 1/1)。

Sample Input

5
1 2 1
1 3 2
1 4 1
2 5 3

Sample Output

13/25

Hint

【样例说明】
13 13 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 ) (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) (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 % 100\% 100% 的数据, n n n 20000 20000 20000

【分析】

题解:点分治模板题

其实这道题和我之前写的一道题很像戳这里

不同的地方就是统计答案时,我们统计一下每个点到重心的距离模 3 3 3 后的值出现的次数,即用 s u m i sum_i sumi 就表示到重心路径模 3 3 3 i i i 的点的总数(当然 i i i 只可能为 0 0 0 1 1 1 或者 2 2 2

那么总共的 a n s = s u m 0 ∗ s u m 0 + 2 ∗ s u m 1 ∗ s u m 2 ans=sum_0*sum_0+2*sum_1*sum_2 ans=sum0sum0+2sum1sum2

后面的 s u m 1 ∗ s u m 2 sum_1*sum_2 sum1sum2 还要乘 2 2 2 的原因是顺序不同的点对是不同的(比如 ( 2 , 3 ) , ( 3 , 2 ) (2,3),(3,2) (2,3),(3,2) 是不同的)

当然由于重复计算,在递归到子树时,还要将部分答案减掉

而总情况显然是 n 2 n^2 n2,因此把 a n s n 2 \frac{ans}{n^2} n2ans 化简一下就可以了

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50005
#define inf (1ll<<31ll)-1
using namespace std;
int t,ans,num,root;
int size[N],Max[N],sum[3];
int first[N],v[N],w[N],next[N];
bool vis[N];
void add(int x,int y,int z)
{
	t++;
	next[t]=first[x];
	first[x]=t;
	v[t]=y;
	w[t]=z;
}
void dfs(int x,int father)
{
	int i,j;
	Max[x]=0,size[x]=1;
	for(i=first[x];i;i=next[i])
	{
		j=v[i];
		if(j!=father&&!vis[j])
		{
			dfs(j,x);
			size[x]+=size[j];
			Max[x]=max(Max[x],size[j]);
		}
	}
}
void find(int rt,int x,int father)
{
	int i,j;
	Max[x]=max(Max[x],size[rt]-size[x]);
	if(num>Max[x])  root=x,num=Max[x];
	for(i=first[x];i;i=next[i])
	{
		j=v[i];
		if(j!=father&&!vis[j])
		  find(rt,j,x);
	}
}
void dist(int x,int father,int len)
{
	int i,j;
	sum[len]++;
	for(i=first[x];i;i=next[i])
	{
		j=v[i];
		if(j!=father&&!vis[j])
		  dist(j,x,(len+w[i])%3);
	}
}
int calc(int x,int l)
{
	memset(sum,0,sizeof(sum));dist(x,0,l%3);
	return sum[0]*sum[0]+2*sum[1]*sum[2];
}
void solve(int x)
{
	int i,j;
	dfs(x,0);
	num=inf,find(x,x,0);
	ans+=calc(root,0);
	vis[root]=true;
	for(i=first[root];i;i=next[i])
	{
		j=v[i];
		if(!vis[j])
		{
			ans-=calc(j,w[i]);
			solve(j);
		}
	}
}
int main()
{
	int n,i,x,y,z;
	scanf("%d",&n);
	for(i=1;i<n;++i)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	solve(1);
	int k=__gcd(ans,n*n);
	printf("%d/%d",ans/k,n*n/k);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值