• [2020多校联考]树上竞技


    Solution

    对每个点进行贡献统计,搞了半天搞不出来。结果是对每条边进行统计,计算会被经过多少次。能想到这个应该是由于题目要求算路径长度,这和每条边被经过的次数挂钩。这个转换真是巧妙。

    首先有一个显然的性质,集合点一定在若干关键点的中间位置。

    考虑枚举每一条边,由于是一棵树,所以断掉这条边会把图分成两半。记其中一边的节点个数为 (s),那么另一边有 (n-s) 个。考虑在其中一边选 (i) 个关键点((iin[1,m-1]),由于一边至少要有一个关键点,不然根本不会经过该条边),有 (inom{s}{i}) 种情况,对于另一边有 (inom{n-s}{m-i}) 种情况。根据上面的性质,集合点的若干子树关键点个数应该尽量平均。那么对于这条枚举的边,集合点一定在关键点多的那一边,所以应该是关键点少的一边走向多的一边,会有 (min{i,m-i}) 的贡献。那么答案就是

    [sum_{(uleftrightarrow v)in E} sum_{i=1}^{m-1} inom{s}{i} inom{n-s}{m-i} min{i,m-i} ]

    最小值不好直接搞,所以考虑分类讨论。会发现折半讨论后,两种情况是相通的,所以现只讨论一种。注意 (m-1) 是奇数的时候会少一种,直接暴力加上即可。式子化简为

    [dots+ imes sum_{i=1}^{lfloor frac{m-1}{2} floor} i inom{s}{i} inom{n-s}{m-i} + [2|m] imes inom{s}{frac{m}{2}} inom{n-s}{frac{m}{2}} frac{m}{2} ]

    后面一坨直接求,可以不看。前面的 (i) 显然可以吸进去,再把吐出来的 (s) 提到式子前面,变成

    [s imes sum_{i=1}^{lfloor frac{m-1}{2} floor} inom{s-1}{i-1} inom{n-s}{m-i} ]

    上标是个常数,为了方便记为 (k)。现在的任务就是求出后面的式子了。设函数 (G(s)) 为后面的一坨,不能直接化简了,所以考虑其组合意义来递推。

    [G(s)=sum_{i=1}^{k} inom{s-1}{i-1} inom{n-s}{m-i} ]

    其组合意义是:有一个长为 (n-1) 的序列,一共选 (m-1) 个数,而在前 (s-1) 个数中至多选 (k-1) 个数,剩下的数在后面选。考虑如何推到 (G(s+1)),其计数在前 (s) 个数中至多选 (k-1),只与 (G(s)) 不同如果在前 (s-1) 个数中选了 (k-1) 个数,位置 (s) 就不能选了,所以减去在前面 (s-1) 个数中强制选 (k-1) 个数,强制选位置 (s),在后面剩下的 (n-s) 个数中随便选 (m-k-1) 个数的方案数。那么

    [G(s+1)=G(s)-inom{s-1}{k-1} inom{n-s}{m-k-1} ]

    可以做到 (O(n)) 递推。最后处理一下边界,即 (G(1))

    [G(1)=sum_{i=1}^{k} inom{0}{i-1} inom{n-1}{m-i} ]

    注意到当 (i) 是大于 (1) 的正整数的时候,(inom{0}{i-1})(0),所以 (G(1)) 就只有一项了。

    [G(1)=inom{n-1}{m-1} ]

    再注意,当 (k=0) 时,有 (G(1)=0)。另一种对应的情况是 (G(n-s))

    #include<stdio.h>
    #define N 1000007
    #define ll long long
    #define Mod 1000000007
    #define re register
    
    inline int read(){
        int x=0,flag=1; char c=getchar();
        while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
        while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
        return flag? x:-x;
    }
    
    struct E{
        int next,to;
    }e[N];
    int head[N],cnt=0;
    
    inline void add(int id,int to){
        e[++cnt]=(E){head[id],to};
        head[id]=cnt;
    }
    
    ll n,m,s[N];
    ll fac[N],K,inv[N],G[N];
    
    ll C(int x,int y){
        if(y<0) return 0;
        if(x<y) return 0;
        if(!x||x==y) return 1;
        return fac[x]*inv[y]%Mod*inv[x-y]%Mod;
    }
    
    ll qpow(ll x,ll y){
        ll ret=1,cnt=0;
        while(y>=(1LL<<cnt)){
            if(y&(1LL<<cnt)) ret=(ret*x)%Mod;
            x=(x*x)%Mod,cnt++;
        }
        return ret;
    }
    
    ll ans=0;
    void dfs(int u){
        s[u]=1;
        for(int i=head[u];i;i=e[i].next){
            int v=e[i].to,S;
            dfs(v);
            s[u]+=(S=s[v]);    
            ans=(ans+G[S]+G[n-S])%Mod;
            if(!(m&1)) ans=(ans+C(S,m/2)*C(n-S,m/2)%Mod*(m/2)%Mod)%Mod;
        }
    }
    
    int main(){
        freopen("meeting.in","r",stdin);
        freopen("meeting.out","w",stdout);
        fac[0]=1;
        for(re int i=1;i<N;i++)
            fac[i]=1LL*fac[i-1]*i%Mod;
        inv[N-1]=qpow(fac[N-1],Mod-2);
        for(re int i=N-1;i>=1;i--)
            inv[i-1]=inv[i]*i%Mod;
        n=read(),m=read();
        for(re int i=2;i<=n;i++) add(read(),i);
        K=(m-1)>>1;
        if(K) G[1]=C(n-1,m-1);
        for(re int i=2;i<=n;i++)
            G[i]=((G[i-1]-C(i-2,K-1)*C(n-i,m-K-1)%Mod)%Mod+Mod)%Mod;
        for(re int i=2;i<=n;i++) G[i]=1LL*G[i]*i%Mod;
        dfs(1);
        printf("%lld",ans);
    }
    
  • 相关阅读:
    登录注册页面切换
    LINUX系统日常使用命令
    find命令详解
    ssh命令详解
    tar命令详解
    route命令详解
    uname命令详解
    ps命令详解
    df命令详解
    virsh命令详解
  • 原文地址:https://www.cnblogs.com/wwlwQWQ/p/14081045.html
Copyright © 2020-2023  润新知