您的位置:首页 > Web前端 > Node.js

51node1677treecnt(组合数学,好题)

2016-12-05 19:21 399 查看
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 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: