题解:luogu 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”)。
输入输出样例
说明
【样例说明】
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)。
【数据规模】
对于100%的数据,n<=20000。
题解:
点分板题,但处理每层暴力n^2两两组合的话会T。
考虑将每个数按模3后的结果分类。显然,若两条权为w1,w2的路径组合后为三的倍数,那么只有三种情况:w1%3=0,w2%3=0或w1%3=1,w2%3=2或w1%3=2,w2%3=1。
这样问题就很简单了:处理每层时,统计模3为1,2,0的路径有多少条,然后直接算就好了,这样单层就可以做到O(n)了
代码如下:
#include<bits/stdc++.h> #define MAXN 40005 #define INF 1e9 using namespace std; struct front_star{ int to,next,w; }edge[MAXN]; int n,cnt=0,k,mx,root,ans=0,tot=1,siz; int head[MAXN],sz[MAXN],temp[MAXN],idx[MAXN]; bool vis[MAXN]; int maxn(int a,int b) { return a>b?a:b; } int Euclid_GCD(int a, int b) { return b?Euclid_GCD(b, a%b):a; } void addedge(int u,int v,int c) { cnt++; edge[cnt].to=v; edge[cnt].w=c; edge[cnt].next=head[u]; head[u]=cnt; } void findroot(int u,int fa) { sz[u]=1; int msz=0; for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].to; if(v!=fa&&!vis[v]) { findroot(v,u); sz[u]+=sz[v]; msz=maxn(msz,sz[v]); } } msz=maxn(msz,siz-sz[u]); if(msz<mx) { mx=msz; root=u; } } void init() { memset(vis,false,sizeof(vis)); memset(head,-1,sizeof(head)); scanf("%d",&n); for(int i=1;i<=n-1;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); addedge(a,b,c); addedge(b,a,c); } siz=n; mx=INF; findroot(1,1); } void dist(int u,int fa) { for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].to; if(!vis[v]&&v!=fa) { tot++; idx[v]=tot; temp[tot]=temp[idx[u]]+edge[i].w; dist(v,u); } } } int count_ans(int u,int val) { tot=1; idx[u]=1; temp[1]=val; dist(u,u); int s1=0,s2=0,s3=0; for(int i=1;i<=tot;i++) { if(temp[i]%3==0) s3++; if(temp[i]%3==1) s1++; if(temp[i]%3==2) s2++; } int ret=s3*s3+2*s1*s2-s3; return ret; } void divide(int u) { ans+=count_ans(u,0); vis[u]=true; for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].to; if(!vis[v]) { ans-=count_ans(v,edge[i].w); siz=sz[v]; mx=INF; findroot(v,u); divide(root); } } } int main() { init(); divide(root); int GCD=Euclid_GCD(ans+n,n*n); printf("%d/%d\n",(ans+n)/GCD,n*n/GCD); return 0; }