-> 题目连接 <-
【题目大意】
给定一棵n个节点的树,从1到n标号。选择k个点,你需要选择一些边使得这k个点通过选择的边联通,目标是使得选择的边数最少。
现需要计算对于所有选择k个点的情况最小选择边数的总和为多少。
1<=k<=n<=100000
【解题思路】
因为这道题要求统计所有k个点的选取方案,暴力枚举肯定不现实,这种情况下一般可以考虑每条边的贡献。
树上的每条边都是桥,所以当且仅当选取的k个点在某条边连接的两个连通块中都有接点,删掉这条边才是合理而且必要的。考虑补集,当这k个点只存在于在某条边连接的两个连通块中的一个中,这条边就不会对答案造成贡献。
假设这条边连接的两个连通块的大小分别为siz和N-siz。那么这条边的贡献就为 Ckn−Cksiz−CkN−siz 。把所有边的贡献加起来即为最终答案。
【代码】
第一次提交之后WA了一大堆点,后来懒得查错了直接写了个#define int long long
然后再把int main()
改成signed main()
就AC了。。这真是个好方法
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
const int maxn=100000+10,MOD=1000000000+7;
typedef long long LLint;
namespace combine{
int fac[maxn];
void exgcd(int a,int b,int& x,int& y){
if(b==0){x=1;y=0;return;}
int x0,y0;exgcd(b,a%b,x0,y0);
x=y0;y=x0-a/b*y0;
}
int inv(int a,int p=MOD){
int x,y;exgcd(a,p,x,y);
return (x%p+p)%p;
}
void init(int n){
fac[0]=1;
for(int i=1;i<=n;i++){
fac[i]=(fac[i-1]*i)%MOD;
}
}
LLint C(int n,int m){
if(n==m || m==0)return 1;
if(n<m)return 0;
return ((long long)fac[n]*inv(fac[n-m])%MOD)
*inv(fac[m])%MOD;
}
}
namespace tree{
int fa[maxn],siz[maxn],ans=0,n,k;
vector<int>G[maxn];
inline void addedge(int f,int t){
G[f].push_back(t);
G[t].push_back(f);
}
int dfs(int x,int f){
#define C combine::C
fa[x]=f;siz[x]=1;
for(int i=0;i<G[x].size();i++){
int u=G[x][i];
if(u==f)continue;
int ssiz=dfs(u,x);
siz[x]+=ssiz;
ans=((((ans+C(n,k))%MOD-C(ssiz,k))%MOD
-C(n-ssiz,k))%MOD+MOD)%MOD;
}
return siz[x];
#undef C
}
}
#include<cctype>
int geti(){
int ans=0,flag=0;char c=getchar();
while(!isdigit(c)){flag|=c=='-';c=getchar();}
while( isdigit(c)){ans=ans*10+c-'0';c=getchar();}
return flag?-ans:ans;
}
void puti(int x){
if(x<0)x=-x,putchar('-');
if(x>9)puti(x/10); putchar(x%10+'0');
}
signed main(){
#define n tree::n
#define k tree::k
n=geti(),k=geti();
combine::init(n);
for(int i=1;i<n;i++){
int f=geti(),t=geti();
tree::addedge(f,t);
}
tree::dfs(1,-1);
printf("%d\n",tree::ans);
#undef n
#undef k
return 0;
}