[2020多校联考]树上竞技

Solution

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

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

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

\[\sum_{(u\leftrightarrow v)\in E} \sum_{i=1}^{m-1} \binom{s}{i} \binom{n-s}{m-i} min\{i,m-i\} \]

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

\[\dots+\times \sum_{i=1}^{\lfloor \frac{m-1}{2} \rfloor} i \binom{s}{i} \binom{n-s}{m-i} + [2|m]\times \binom{s}{\frac{m}{2}} \binom{n-s}{\frac{m}{2}} \frac{m}{2} \]

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

\[s\times \sum_{i=1}^{\lfloor \frac{m-1}{2} \rfloor} \binom{s-1}{i-1} \binom{n-s}{m-i} \]

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

\[G(s)=\sum_{i=1}^{k} \binom{s-1}{i-1} \binom{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)-\binom{s-1}{k-1} \binom{n-s}{m-k-1} \]

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

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

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

\[G(1)=\binom{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);
}
posted @ 2020-12-03 16:25  Kreap  阅读(111)  评论(0编辑  收藏  举报