51node1677treecnt(组合数学,好题)

来源:互联网 发布:部落冲突 镜面法术数据 编辑:程序博客网 时间:2024/05/05 20:31

treecnt
﹡    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 21 21 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;}


0 0
原创粉丝点击