HYSBZ 2152,点分治

聪聪可可

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

思路:点分治时每次统计出到重心距离%3为0,1,2的路径数,因为两人选取顺序有影响,故 a n s + = ( n u m [ 0 ] n u m [ 0 ] + n u m [ 1 ] n u m [ 2 ] 2 ) ans += (num[0] * num[0]+num[1] * num[2] * 2) ,总的选取方案数为n*n,两者除以它们的gcd即可

#include<bits/stdc++.h>
#define MAXN 20010
#define INF 0x3f3f3f3f
using namespace std;
int head[MAXN],tot;
struct edge
{
    int v,w,nxt;
}edg[MAXN << 1];
inline void addedg(int u,int v,int w)
{
    edg[tot].v = v;
    edg[tot].w = w;
    edg[tot].nxt = head[u];
    head[u] = tot++;
}
int n,root,ms,mson[MAXN],sz[MAXN],Size;
bool vis[MAXN];
//root用于标记重心,ms表示树的重心的最大子树的大小,mson[i]记录以i为根最大子树的大小
//sz[i]记录以i为根子树的大小,Size表示当前整棵树的大小,vis[i]表示当前节点是否被分治过
void getroot(int u,int f)//获得重心
{
    sz[u] = 1,mson[u] = 0;
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v] || v == f) continue;//剔除已经被分治过的点
        getroot(v,u);
        sz[u] += sz[v];
        if(sz[v] > mson[u]) mson[u] = sz[v];
    }
    if(Size - sz[u] > mson[u]) mson[u] = Size-sz[u];//把u看作根节点时u的父亲那一部分也算作子树
    if(ms > mson[u]) ms = mson[u],root = u;//更新重心
}
int dis[MAXN],cnt;//dis记录所有节点到重心的距离
int ans,num[3];
void getdis(int u,int f,int d)//获得到目标点的距离
{
    dis[++cnt] = d;
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v] || v == f) continue;
        getdis(v,u,d + edg[i].w);
    }
}
void cal(int u,int d,int tp)//u表示getdis的起点,d表示u到目标点的距离,tp表示这一次统计出来的答案是合理的还是不合理的
{
    cnt = 0;
    getdis(u,0,d);//算出树中的点到目标点的距离
    sort(dis+1,dis+cnt+1);
    num[0] = num[1] = num[2] = 0;
    for(int i = 1;i <= cnt;++i)
        ++num[dis[i]%3];
    ans += (num[0]*num[0]+num[1]*num[2]*2)*tp;
}
void solve(int u,int ssize)//ssize是当前这棵子树的大小
{
    vis[u] = true;//代码保证每次进来的u都必定是当前这棵树的重心,我们将vis[u]标记为true,表示u点被分治过
    cal(u,0,1);//计算这棵树以u为重心的所有组合,但包括了共用同一条边的情况
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v]) continue;
        cal(v,edg[i].w,-1);//将共用一条边的不合法情况去除
        ms = INF;//记得每次都要初始化
        Size = sz[v] < sz[u]?sz[v]:(ssize-sz[u]);//因为v实际上可能是u的父亲,故sz需相减
        getroot(v,v);//求出以v为根节点的子树重心
        solve(root,Size);
    }
}
inline void init()
{
    tot = 0,ms = INF,Size = n;
    memset(head,-1,sizeof(int)*(n+1));
    memset(vis,false,sizeof(bool)*(n+1));
    ans = 0;
}
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
int main()
{
    while(~scanf("%d",&n))
    {
        init();
        int u,v,w;
        for(int i = 1;i < n;++i)
        {
            scanf("%d%d%d",&u,&v,&w);
            addedg(u,v,w%3),addedg(v,u,w%3);
        }
        getroot(1,1);
        solve(root,Size);
        int tmp = n * n;
        int g = gcd(tmp,ans);
        printf("%d/%d\n",ans/g,tmp/g);
    }
    return 0;
}
发布了50 篇原创文章 · 获赞 3 · 访问量 3101

猜你喜欢

转载自blog.csdn.net/xing_mo/article/details/104008252
今日推荐