51nod 1677 treecnt

给定一棵n个节点的树,从1到n标号。选择k个点,你需要选择一些边使得这k个点通过选择的边联通,目标是使得选择的边数最少。

现需要计算对于所有选择k个点的情况最小选择边数的总和为多少。

样例解释:


一共有三种可能:(下列配图蓝色点表示选择的点,红色边表示最优方案中的边)

选择点{1,2}:至少要选择第一条边使得1和2联通。

 

选择点{1,3}:至少要选择第二条边使得1和3联通。

 

选择点{2,3}:两条边都要选择才能使2和3联通。

 

Input
第一行两个数n,k(1<=k<=n<=100000)
接下来n-1行,每行两个数x,y描述一条边(1<=x,y<=n)
Output
一个数,答案对1,000,000,007取模。
Input示例
3 2
1 2
1 3
Output示例4


题目大意:
一棵树上,选k个点,用最少的边把这k个点联通,将边数计入答案,求所有
情况累计的答案和。

题解:卢卡斯定理+统计
考虑每条边对答案的贡献。
当切去某一条边时,树会分成两部分。只有当选中的k个点完全在两部分中的
一个时,这条边对这k个点没有贡献。总共的情况数为C(n,k),减去没有贡献
的情况C(size[x],k),和C(n-size[x],k)就是这条边出现的次数。其中
size[x]
为以切去的这条边的深度较深的端点为根的子树的个数。组合数用卢卡
斯定理处理,预处理逆元。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
#define maxn 100008
#define mod 1000000007 
using namespace std;

LL n,k,sumedge,ans;
LL head[maxn],size[maxn],f[maxn],inv[maxn];

struct Edge{
    int x,y,nxt;
    Edge(int x=0,int y=0,int nxt=0):
        x(x),y(y),nxt(nxt){}
}edge[maxn<<1];

void add(int x,int y){
    edge[++sumedge]=Edge(x,y,head[x]);
    head[x]=sumedge;
}

LL ksm(LL x,LL y){
    LL ret=1;
    while(y){
        if(y&1) ret=ret*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ret;
}


void pre(){
    f[0]=inv[0]=1;
    for(int i=1;i<=maxn;i++){
        f[i]=(f[i-1]*i)%mod;
        inv[i]=ksm(f[i],mod-2);
    }
}

LL Lucas(LL n,LL m){
    if(m>n)return 0;
    if(m==n)return 1;
    return f[n]*inv[m]%mod*inv[n-m]%mod;
}

void dfs(int x,int fa){
    size[x]=1;
    for(int i=head[x];i;i=edge[i].nxt){
        int v=edge[i].y;
        if(v==fa)continue;
        dfs(v,x);
        size[x]+=size[v];
    }
    ans=(ans+Lucas(n,k)-Lucas(size[x],k)-Lucas(n-size[x],k)+mod)%mod;
}

int main(){
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    pre();dfs(1,-1);
    cout<<ans<<endl;
    return 0;
}


 
原文地址:https://www.cnblogs.com/zzyh/p/7644323.html