首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >洛谷 2634&&BZOJ 2152: 聪聪可可【点分治学习+超详细注释】

洛谷 2634&&BZOJ 2152: 聪聪可可【点分治学习+超详细注释】

作者头像
Angel_Kitty
发布2018-04-09 15:33:38
4940
发布2018-04-09 15:33:38
举报

2152: 聪聪可可

Time Limit: 3 Sec  Memory Limit: 259 MB

Submit: 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

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

Sample Output

13/25 【样例说明】 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 }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-08-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2152: 聪聪可可
  • Description
  • Input
  • Output
  • Sample Input
  • Sample Output
  • HINT
  • Source
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档