题目大意:有两个小孩在玩游戏,他们每一个人在树中取一个点,如果这两个点之间的路径长度之和是3的倍数,那么聪聪就赢了,否则他就输了。给出这棵树,求聪聪赢的概率,答案用分数表示。
思路:数据范围2w,肯定不能枚举点然后LCA。所以就只能点分治了。这还是一道比较常规的点分治问题,但是有一个地方需要注意,在统计两点之间的距离的时候我一开始的想法是直接n^2的枚举,然后记录。但是那样时间复杂度就会严重退化。但是注意到我们只需要%3==0的数的个数,和这个有关的之后%3==0,%3==1,%3==2的数。所以在统计的时候我们只要统计这三种数字的个数就可以了。
PS:用了algorithm中的__gcd函数,据说考试的时候不让用。。。
CODE:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 20010 #define INF 0x3f3f3f3f using namespace std; int points; int head[MAX],total; int next[MAX << 1],aim[MAX << 1],length[MAX << 1]; int size[MAX],_size,_total,c; bool v[MAX]; int cnt[MAX],dis[3],p; inline void Add(int x,int y,int len) { next[++total] = head[x]; aim[total] = y; length[total] = len % 3; head[x] = total; } inline void GetRoot(int x,int last) { size[x] = 1; int max_size = 0; for(int i = head[x]; i; i = next[i]) { if(aim[i] == last || v[aim[i]]) continue; GetRoot(aim[i],x); size[x] += size[aim[i]]; max_size = max(max_size,size[aim[i]]); } max_size = max(max_size,_total - size[x]); if(_size > max_size) _size = max_size,c = x; } inline void GetDis(int x,int last,int len) { ++dis[len % 3]; for(int i = head[x]; i; i = next[i]) { if(aim[i] == last || v[aim[i]]) continue; GetDis(aim[i],x,len + length[i]); } } inline int Count(int x,int len) { int re = 0; p = 0; memset(dis,0,sizeof(dis)); GetDis(x,0,len); re += dis[0] * dis[0]; re += dis[1] * dis[2] << 1; return re; } inline void Work(int x) { _size = INF; _total = size[x] ? size[x]:points; GetRoot(x,0); x = c; v[x] = true; cnt[x] = Count(x,0); for(int i = head[x]; i; i = next[i]) { if(v[aim[i]]) continue; cnt[x] -= Count(aim[i],length[i]); Work(aim[i]); } } int main() { cin >> points; for(int x,y,z,i = 1; i < points; ++i) { scanf("%d%d%d",&x,&y,&z); Add(x,y,z),Add(y,x,z); } Work(1); int ans = 0; for(int i = 1; i <= points; ++i) ans += cnt[i]; cout << ans / __gcd(ans,points * points) << '/' << points * points / __gcd(ans,points * points) << endl; return 0; }