51node1677treecnt(组合数学,好题)

34 篇文章 0 订阅
13 篇文章 0 订阅

﹡    LH  (命题人)
基准时间限制:1 秒 空间限制:131072 KB 分值: 40

给定一棵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

题解:

可以对每条边进行考虑:

对任意边(u,v) 
设a=以v为根的子树的结点数
b=n-a 
那这条边被选择的次数=C(a,1)*C(b,k-1)+C(a,2)*C(b,k-2)+C(a,3)*C(b,k-3)+….. 
显然 这样肯定会TLE 
不妨换个角度 
考虑从n个点中选择k个点 一共有C(n,k)总情况 
当k个点全在a中选出来 或 k个点全在b中选出来的情况是要排除的 

所以这条边被选择的次数为C(n,k)-C(a,k)-C(b,k)。

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int MAXN=1e5+100;
typedef long long ll;
const ll mod=1e9+7;
struct node
{
    int to,next;
}e[2*MAXN];
int head[MAXN];
int tol=0;
ll f[MAXN],inv[MAXN];
int n,k;
void add(int u,int v)
{
    e[++tol].to=v,e[tol].next=head[u],head[u]=tol;
}
ll pow(ll a,ll b)
{
    ll ans=1,tmp=a;
    while(b)
    {
        if(b&1) ans=(ans*tmp)%mod;
        tmp=(tmp*tmp)%mod;
        b/=2;
    }
    return ans;
}
ll ans=0;
ll C(ll a,ll b)
{
    if(a<b) return 0;
    return (f[a]*inv[a-b])%mod*inv[b]%mod;
}

int dfs(int u,int f)
{
    int cnt=0;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==f) continue;
        int t=dfs(v,u);
        ans=(ans+C(n,k)-C(t,k)-C(n-t,k)+mod+mod)%mod;
        cnt+=t;
    }
    return cnt+1;
}
int main(int argc, const char * argv[]) {
    f[0]=1;
    for(int i=1;i<MAXN;i++) f[i]=(f[i-1]*i)%mod;
    for(int i=0;i<MAXN;i++)
    {
        inv[i]=pow(f[i],mod-2);
    }
    while(~scanf("%d%d",&n,&k))
    {
        tol=0;
        memset(head,0,sizeof(head));
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v),add(v,u);
        }
        //puts("1");
        ans=0;
        dfs(1,-1);
        printf("%lld\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值