题意:给你n个结点,n-1条边,要从这n个结点里面选出k个结点,再选出最少边使这些结点之间相互连通,问对于所有选择k个结点的最小选择边数的总和是多少。
题解:因为每次选k个点其实是固定了的,所以撇开要确定的边,每次要选的k个点就是一个组合数问题,那怎么把点和边联系起来呢?对于任意一条边,它要不要被选入作为连接点的边是由它两边的点决定的。当只有这条边左边或者右边的点被选入时,这条边是不需要的,只有当两端的两个点同时被选入,这条边才会对答案有贡献。
而判断这条边两端的点被选入的情况时,只用分三种情况:
① 在这条边左边选了k个点,这条边不计入答案,无贡献。
② 在这条边右边选了k个点,这条边不计入答案,无贡献。
③ 在这条边两端都选了点,这条边必然记入答案,贡献加1。
所以根据上面的情况我们容易得到 对于每一条边它的贡献是:ans=C(n,k)-C(t,k)-C(n-t,k),这里的t就是该边左边的结点数。已经知道这个式子了,我们只需要统计每条边左边和右边的结点总数,然后依次带入式子把答案求出来就行了。由于题目要求取模,阶乘的数又比较大所以要用到阶乘逆元(套个阶乘逆元的板子就行了)。
附上代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const ll maxn=1000010;
ll fac[maxn]; //阶乘
ll inv[maxn];//阶乘的逆元
ll n,k,ans,ss;
vector<ll>q[maxn];
ll qpow(ll x,ll n)
{
ll ret=1;
for(; n; n>>=1)
{
if(n&1)
ret=ret*x%mod;
x=x*x%mod;
}
return ret;
}
void getjni()
{
fac[1]=1;
for(int i=2; i<=maxn; i++)//预处理阶乘
fac[i]=fac[i-1]*i%mod;
inv[maxn]=qpow(fac[maxn],mod-2);//求阶乘的逆元
for(int i=maxn-1; i>=0; i--)
inv[i]=inv[i+1]*(i+1)%mod;
}
ll C(ll a,ll b)
{
if(b>a)
return 0;
if(b==0)
return 1;
return fac[a]*inv[b]%mod*inv[a-b]%mod;//计算组合数
}
ll dfs(int st,int en)
{
ll cnt=1;
for(int i=0; i<q[st].size(); i++)
{
if(q[st][i]==en) continue;
ll t=dfs(q[st][i],st);//找左边有多少个结点
cnt+=t;
ans=(ans+ss-C(t,k)-C(n-t,k))%mod;
}
return cnt;
}
int main()
{
getjni();
while(scanf("%lld%lld",&n,&k)!=EOF)
{
ans=0;
ss=C(n,k);
for(int i=1; i<=n; i++)
q[i].clear();
for(int i=1; i<n; i++)
{
ll u,v;
scanf("%lld%lld",&u,&v);
q[u].push_back(v);
q[v].push_back(u);
}
dfs(1,-1);
printf("%lld\n",(ans+mod)%mod);
}
}