51nod 1677——treecnt

题意:给你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);
    }
}

猜你喜欢

转载自blog.csdn.net/wookaikaiko/article/details/81076799