洛谷 2634&&BZOJ 2152: 聪聪可可【点分治学习+超详细注释】
2152: 聪聪可可
Time Limit: 3 Sec Memory Limit: 259 MBSubmit: 3435 Solved: 1776
[Submit][Status][Discuss]
Description
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
Input
输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。
Output
以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。
Sample Input
1 2 1
1 3 2
1 4 1
2 5 3
Sample Output
【样例说明】
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。
HINT
Source
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2152
一道裸的树分治
令节点 i 到当前分治的节点的距离为 dis[i] ,对于任意一个满足条件的点对 [i,j] ,有 (dis[i] + dis[j]) % 3 = 0
我们将所有点的 dis[] 值对 3 取余,统计出取余后结果为 0,1,2 的个数,记为 num[0], num[1], num[2]。
那么,对于子树中任意的一个节点 i :
1)dis[i] % 3 = 0 时:当前符合条件的点对数为 num[0](即同为 3 的倍数);
2)dis[i] % 3 = 1 时:当前符合条件的点对数为 num[3 - 1 = 2] 。
(证明:将 dis[i] 拆成 3x + 1,满足条件的另一个点的距离 dis[j] 拆成 3y + 2,则和为 3x + 1 + 3y + 2 = 3x + 3y + 3 = 3(x + y + 1),是 3 的倍数)
3)dis[i] % 3 = 2 时:当前符合条件的点对数为 num[3 - 2 = 1] 。(证明同上)
以上情况可合并为 cnt += (!dis[i] ? num[0] : num[3 - dis[i]])(cnt 为记录的答案,dis[i] 已经对 3 取余过)
下面给出AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 inline int read()//读入优化 4 { 5 int x=0,f=1;//f表示符号,x表示首位数字0 6 char ch=getchar(); 7 while(ch<'0'||ch>'9')//如果ch不是数字 8 { 9 if(ch=='-')//如果是符号就改变符号 10 f=-1; 11 ch=getchar(); 12 } 13 while(ch>='0'&&ch<='9')//如果ch是数字,接下来的每位数字 14 { 15 x=x*10+ch-'0';//将数字添加进x内 16 ch=getchar(); 17 } 18 return x*f;//返回数值 19 } 20 inline void write(int x)//输出优化 21 { 22 if(x<0)//判断小于0的情况 23 { 24 putchar('-'); 25 x=-x; 26 } 27 if(x>9)//保存每一位 28 { 29 write(x/10); 30 } 31 putchar(x%10+'0');//输出 32 } 33 inline int gcd(int a,int b)//求最大公因数 34 { 35 return b==0?a:gcd(b,a%b); 36 } 37 const int N=20010; 38 int last[N]; 39 int son[N];//son表示树的大小 40 int f[N];//表示最大子树的节点数 41 int d[N];//表示到k的距离 42 int t[N];//表示到k的距离%3=0的点的个数 43 bool vis[N]; 44 struct Edge//前向星存边 45 { 46 int to,next,v; 47 }edge[N<<1];//保存双向图 48 int n,cnt,ans,root,sum; 49 inline void addage(int u,int v,int w)//连双向边 50 { 51 edge[++cnt].to=v; 52 edge[cnt].next=last[u]; 53 last[u]=cnt; 54 edge[cnt].v=w; 55 edge[++cnt].to=u; 56 edge[cnt].next=last[v]; 57 last[v]=cnt; 58 edge[cnt].v=w; 59 } 60 inline void getroot(int x,int fa)//寻找根节点,根节点满足最大儿子子树规模最小,求重心的操作 61 { 62 son[x]=1;//son[x]表示x的树大小 63 f[x]=0;//f[x]表示x最大子树的节点数 64 for(int i=last[x];i;i=edge[i].next)//枚举和x相邻的每一个点 65 { 66 if(!vis[edge[i].to]&&edge[i].to!=fa)//如果没有被删除,并且当前节点不是根节点 67 { 68 getroot(edge[i].to,x); 69 son[x]+=son[edge[i].to];//子树规模 70 f[x]=max(f[x],son[edge[i].to]); 71 } 72 } 73 f[x]=max(f[x],sum-son[x]);//x最大子树的节点数f[x]=max(f[x],与此子树大小-f[x]) 74 if(f[x]<f[root]) 75 root=x; 76 } 77 inline void getdeep(int x,int fa)//获得每个点到cal中的x的距离,即root 78 { 79 t[d[x]]++;//统计到k距离的个数,//将对应余数的数目+1 80 for(int i=last[x];i;i=edge[i].next)//枚举和x相邻的每一个点 81 { 82 if(!vis[edge[i].to]&&edge[i].to!=fa)//如果没有被删除,并且当前节点不是根节点 83 { 84 d[edge[i].to]=(d[x]+edge[i].v)%3; 85 getdeep(edge[i].to,x); 86 } 87 } 88 } 89 inline int cal(int x,int now)//t[0]表示到k的距离%3=0的点的个数,t[1]表示余数为1,t[2]表示余数为2,所以计算方案数时,t[0]内部解决,t[1]和t[2]两两搭配 90 { 91 t[0]=t[1]=t[2]=0;//余数清0 92 d[x]=now; 93 getdeep(x,0);//getdeep更新子树的root的值,计算深度root 94 return t[1]*t[2]*2+t[0]*t[0];//计算路径数 95 } 96 inline void work(int x)//表示work以x为根的子树,此时x已经是重心 97 { 98 ans+=cal(x,0);//统计不同子树通过重心的个数 99 vis[x]=1;//把x从树中删除 100 for(int i=last[x];i;i=edge[i].next)//枚举和x相邻的每一个点 101 { 102 if(!vis[edge[i].to])//如果没有被删除,说明在某一棵子树中 103 { 104 ans-=cal(edge[i].to,edge[i].v);//去除在同一个子树中被重复统计的 105 root=0; 106 sum=son[edge[i].to]; 107 getroot(edge[i].to,0);//找到所在子树的重心root,更新重心root 108 work(root);//递归处理root,求解子树 109 } 110 } 111 } 112 int main() 113 { 114 n=read(); 115 for(int i=1;i<n;i++)//建图 116 { 117 int u=read(); 118 int v=read(); 119 int w=read()%3; 120 addage(u,v,w);//建立一个无向图 121 } 122 f[0]=n; 123 sum=n; 124 getroot(1,0); 125 work(root); 126 int t=gcd(ans,n*n); 127 printf("%d/%d\n",ans/t,n*n/t); 128 return 0; 129 }
作 者:Angel_Kitty
出 处:https://www.cnblogs.com/ECJTUACM-873284962/
关于作者:阿里云ACE,目前主要研究方向是Web安全漏洞以及反序列化。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是作者坚持原创和持续写作的最大动力!
欢迎大家关注我的微信公众号IT老实人(IThonest),如果您觉得文章对您有很大的帮助,您可以考虑赏博主一杯咖啡以资鼓励,您的肯定将是我最大的动力。thx.
我的公众号是IT老实人(IThonest),一个有故事的公众号,欢迎大家来这里讨论,共同进步,不断学习才能不断进步。扫下面的二维码或者收藏下面的二维码关注吧(长按下面的二维码图片、并选择识别图中的二维码),个人QQ和微信的二维码也已给出,扫描下面👇的二维码一起来讨论吧!!!
欢迎大家关注我的Github,一些文章的备份和平常做的一些项目会存放在这里。