CF1824B2 LuoTianyi and the Floating Islands题解

是 Div2 的 D1 和 D2。

题意

给定一棵 n 个结点的树,现在有 k(kn) 个结点上有人。
一个结点是好的当且仅当这个点到所有人的距离之和最小。
求在这 n 个点中随机取 k 个点时,好的结点的期望个数,对 109+7 取模。

Easy: k3,n2105
Hard: kn2105

Part 1

考虑 Easy 版本。

  • k=1 时,只有一个点是关键点,故答案为 1
  • k=3 时,考虑如下三种情况:
    image
    当钦定 12 两个点后,考虑第三个点的位置。
  1. 当三者在一条链上,且第三个点不在前两个点中间时:显然,中间的点是唯一一个“好点”。
  2. 第三个点在两者中间时:同上,中间的点是唯一一个“好点”。
  3. 三者不在一条链上:最上方的点是唯一一个“好点”。
    得出结论:k=3 时答案也为 1
  • k=2 时,对于每一条链,中间的每一个点都可以成为“好点”。所以只需要统计各个长度的链共有多少条,简单计数即可。

Part 2

进阶到 Hard 版本。
猜想:当 k 为奇数时,答案恒为 1
证明:称每个点到选定的 k 个点的距离之和称为距离和。假设目前 xy 两个点都是“好点”,且钦定 x 为这棵树的根,来考虑 x 移动到 y 过程中距离和的变化。设当前的距离和为 dis,当前点子树内共有 s 个特殊点,那么从 x 移动到其某一个儿子后,距离和改变为 diss+(ks)=dis+(k2s)。与子树内的特殊点的距离均 1,与子树外的特殊点距离均 +1。又由于 x 为“好点”,则此时 k2s0。而 k 是奇数,所以 k2s>0。又由于这个变化量是所有的变化量中最小的,故移动到点 y 时,距离和一定会比 dis 大,与题设矛盾,所以猜想成立。

k 为偶数时呢?显然,变化量要为 0。则可得到 k=2s。转化到题目中,则好点 x 为子树中的一部分k/2 个特殊点,且子树外也有 k/2 个特殊点的点。还可以通过这个结论,结合 k 为奇数的证明步骤得到:若存在这样的点,则类似的“好点”都是串联在一起的,且数量 2。而通过这种方法计数得到的“好点”数量,本质上是将视角转换为了从边的角度来计数,所以计算出的答案会少 1。故最后的答案应加上 1

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define db double
#define mkp make_pair
#define pb push_back
#define P pair<int,int>
#define _ 0
const int N=2e5,mod=1e9+7,MOD=1e9+123,inf=1e18;
int T=1,siz[N+10],ans,fac[N+10],inv[N+10],n,k;
vector<int> e[N+10];
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1) ans=(ans*a)%mod;
        b>>=1;
        a=(a*a)%mod;
    }
    return ans;
}
void init(){
    fac[0]=1;
    for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i%mod;
    inv[N]=qpow(fac[N],mod-2);
    for(int i=N-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int n,int m){
    if(n<m||n<0||m<0) return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void dfs(int x,int fa){
    siz[x]=1;
    for(int y:e[x]){
        if(y==fa) continue;
        dfs(y,x);
        siz[x]+=siz[y];
        ans=(ans+C(siz[y],k/2)*C(n-siz[y],k/2))%mod;
    }
}
void solve(){
    init();
    cin>>n>>k;
    if(k&1){
        cout<<1;
        return ;
    }
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        e[u].pb(v),e[v].pb(u);
    }
    ans=C(n,k);
    dfs(1,0);
    cout<<ans*qpow(C(n,k),mod-2)%mod;
}
signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    // ios::sync_with_stdio(false),cin.tie(0);
    while(T--){
        solve();
    }
    return ~~(0^_^0);
}

作者:ryder

出处:https://www.cnblogs.com/ryder/p/17384311.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Ryder00  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示