zoukankan      html  css  js  c++  java
  • UOJ351 新年的叶子

    UOJ351 新年的叶子

    题目传送门

    题意

    躲过了(AlphaGo)之后,你躲在(SingleDog)的长毛里,和它们一起来到了(AlphaGo)的家。此时你们才突然发现,(AlphaGo)的家居然是一个隐藏在地下的计算中心!难道(AlphaGo)如此人赢的秘密是...它其实是一个(AI)
    根据情报,这个地下的计算中心的结构构成了一棵无根树,整个计算中心名为被(AT-field)的力场保护起来,保护力场的直径恰好等于树的直径(树的直径定义为树上距离最远的两个结点之间的距离,结点 (u),(v) 的距离定义为从 (u)(v) 最少需要经过的边数)。
    由于保护力场的存在,(SingleDog)们每次只能进入整棵树的一个叶子(度为1的结点),由于狗的大脑很小,他们每次只会随机进攻一个原树的叶子,并且破坏里面的设备,更加狼狈的是他们甚至会重复进入一个已经被破坏过的叶子。
    他们想知道,期望多少次之后,可以使得保护力场的直径缩小?
    即,对于一棵树,每次随机染黑一个叶子(可能会重复染黑),期望多少次后直径变小?

    输入格式
    从标准输入读入数据。
    第一行一个正整数(n),表示树的点数。
    接下来(n−1)行,每行两个正整数(x),(y),表示树上的一条边。
    输出格式
    输出到标准输出。
    输出一行一个整数表示答案在模 (998244353) 意义下的取值。
    即设答案化为最简分式后的形式为 (frac{a}{b}) ,其中 (a)(b) 互质。输出整数 (x) 使得 (x equiv a pmod{998244353})(0 leq x < 998244353)。可以证明这样的整数 (x) 是唯一的。

    题解

    首先一点可以发现的是,我们所需要的点只是所有直径的两个端点,而其余的都是没有用的点,因为改变了其余的点,是不能够达成第一次改变树的直径的条件的。然后我们分直径的奇偶进行讨论:

    • 如果直径为奇数,那么我们一定可以找到中间的那条边,满足所有的直径都经过那一条边。那么我们会发现,所有的有用点会被分为两个点集,分别是在中间那条边的左边与右边。直径会发生改变当且仅当只剩下了某一个点集。
    • 如果直径为偶数,那么我会可以找到中间的一个点,满足所有的直径都经过这个点。那么我们以这个点为根,构建一棵有根树,所有的有用点会根据其所处于根节点的哪棵子树里被划分成若干个点集。所以所有的直径都是从某一个点集中的一个点出发,到达另一个点集中,直径会发生改变当且仅当我们删除到只剩下一个点集。

    现在我们已经将所有有用的叶子节点分成了若干个集合,所以我们开始计算每一个集合的贡献(即最后只剩下这个集合)。这里,我们设这个集合的点数为(d),所有集合总点数为(d0),叶子节点的总点数为(m)
    首先我们枚举我们所选的集合最后会剩下多少个点(i (1 leq i leq d0-1)),那么这(i)个点的总选择方案为( binom{d0}{i})。由于我们要求删掉所有集合只剩下所选的集合,并且为了满足是在删掉最后一个点之后才是第一次改变直径,我们最后一个删掉的点一定不能是所选集合中的点。然后我们考虑前面删点的方案,这个随机删点的方法似乎比较的不靠谱,于是我们考虑转化一下问题。我们假设当前还有(x)个叶子节点没有删,那么删掉一个叶子节点的代价是(frac{m}{x}),那么问题就转化成了删掉每一个点有一个代价,要求给定一个删除的方案,求总代价。然后这个方案数实际上就是待删除节点的阶乘。所以我们现在总共要删掉的节点数是(d0-d+i-1)(减一是因为有一个非所选集合的点一定要最后删,这个点的位置已经定下来了),总共的方案数为((d0-d+i-1)!),然后最后删除的点的选择方案有((d0-d))中,所以实际的方案数是((d0-d+i-1)!*(d0-d)),删除这些节点的总共代价是(sum_{j=d-i+1}^{d0}frac{m}{j})。删除这些节点之后,由于我们的操作次数是无限次,所以所有的叶子节点最后都一定会被删除掉,那么剩余的那些没有用的叶子节点总共的删除方案个数为((d-i)!)种。最后要求的是期望,所以还要除以一个总共的方案数(d0!)
    所以最后对于某一个集合的答案计算的公式如下:
    (sum_{i=1}^{d} binom{d0}{i}*(d0-d+i-1)!*(d0-d)*(sum_{j=d-i+1}^{d0}frac{m}{j})*(d-i)!*frac{1}{d0!})
    里面的阶乘,阶乘逆元,(sum_j frac{m}{j})可以预处理,组合数可以直接算,总共的复杂度为(O(n))

    Code:

    #pragma GCC optimize (2,"inline","Ofast")
    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e5+500;
    const int Md=998244353;
    typedef long long ll;
    int n;
    int fac[N],Inv[N];
    inline int Add(const int &x,const int &y) {return x+y>=Md?(x+y-Md):(x+y);}
    inline int Sub(const int &x,const int &y) {return x-y<0?(x-y+Md):(x-y);}
    inline int Mul(const int &x,const int &y) {return (ll)x*y%Md;}
    int Powe(int x,int y) {
      int res=1;
      while(y) {
        if(y&1) res=Mul(res,x);
        x=Mul(x,x);
        y>>=1;
      }
      return res;
    }
    
    struct edge {
      int to,nxt;
    }E[N<<2];
    int tot,setc,Hd,totc,totlf;
    int head[N],dis[N],fa[N],cnt[N],vis[N],isf[N],in[N],sum[N],inv[N];
    void Addedge(int u,int v) {
      E[++tot].to=v;E[tot].nxt=head[u];head[u]=tot;
      E[++tot].to=u;E[tot].nxt=head[v];head[v]=tot;
    }
    
    void Dfs(int o,int ff,int d) {
      dis[o]=d;fa[o]=ff;
      for(int i=head[o];~i;i=E[i].nxt) {
        int to=E[i].to;
        if(to==ff) continue;
        Dfs(to,o,d+1);
      }
    }
    
    void Get(int o,int ff,int ds,int st) {
      if(isf[o]&&ds==Hd) cnt[st]++,totc++;
      vis[o]=1;
      for(int i=head[o];~i;i=E[i].nxt) {
        int to=E[i].to;
        if(to==ff||vis[to]) continue;
        Get(to,o,ds+1,st);
      }
    }
    
    void Init() {
      fac[0]=1;
      for(int i=1;i<=500000;i++) {
        fac[i]=(ll)fac[i-1]*i%Md;
      }
      Inv[500000]=Powe(fac[500000],Md-2);
      for(int i=500000-1;i;i--) {
        Inv[i]=(ll)Inv[i+1]*(i+1)%Md;
      }
      Inv[0]=1;
      sum[1]=1;
      inv[0]=inv[1]=1;
      for(int i=2;i<=n;++i) {
        inv[i]=Mul(inv[Md%i],(Md-Md/i));
        sum[i]=Add(sum[i-1],inv[i]);
      }
    }
    
    int C(int n,int m) {
      assert(n>=m);
      return (ll)fac[n]*Inv[m]%Md*Inv[n-m]%Md;
    }
    
    int main() {
      memset(head,-1,sizeof head);
      scanf("%d",&n);
      Init();
      for(int i=1,u,v;i<n;i++) {
        scanf("%d%d",&u,&v);
        Addedge(u,v);
        in[u]++;in[v]++;
      }
      for(int i=1;i<=n;i++) {
        if(in[i]==1) {
          isf[i]=1;
          totlf++;
        }
      }
      Dfs(1,1,0);
      int Mx=1;
      for(int i=1;i<=n;i++) {
        if(dis[i]>dis[Mx]) Mx=i;
      }
      Dfs(Mx,Mx,0);
      Mx=1;
      for(int i=1;i<=n;i++) {
        if(dis[i]>dis[Mx]) Mx=i;
      }
      int D=dis[Mx];
      Hd=dis[Mx]>>1;
      for(int i=1;i<=Hd;i++) Mx=fa[Mx];
      if(!(D&1)) {
        for(int i=head[Mx];~i;i=E[i].nxt) {
          int to=E[i].to;
          vis[to]=1;
          Get(to,Mx,1,++setc);
          if(!cnt[setc]) --setc;
        }
      }
      else {
        vis[Mx]=vis[fa[Mx]]=1;
        Get(Mx,fa[Mx],0,++setc);
        if(!cnt[setc]) --setc;
        Get(fa[Mx],Mx,0,++setc);
        if(!cnt[setc]) --setc;
      }
      int res=0;
      int d0=totc,m=totlf;
      for(int i=1;i<=setc;i++) {
        int d=cnt[i];
        for(int j=0;j<cnt[i];j++) {
          int ans=Mul(C(d,j),fac[d0-d+j-1]);
          ans=Mul(ans,d0-d);
          int S=Mul(Sub(sum[d0],sum[d-j]),m);
          ans=Mul(ans,S);
          ans=Mul(ans,fac[d-j]);
          ans=Mul(ans,Inv[d0]);
          res=Add(res,ans);
        }
      }
      printf("%d
    ",res);
      return 0;
    }
    
  • 相关阅读:
    伪静态
    query 文件提交 +php后台入库
    PHP实现URL长连接转短连接方法
    通过经纬度获取所属城市信息
    PHP 学习之路1
    Linux云服务器安装tomcat
    linux下svn服务器搭建步骤
    HttpServletrequest 与HttpServletResponse总结
    HTML5基础
    Ajax相关总结
  • 原文地址:https://www.cnblogs.com/Apocrypha/p/9913045.html
Copyright © 2011-2022 走看看