-> 題目連接 <-
【題目大意】
給定一棵n個節點的樹,從1到n標號。選擇k個點,你需要選擇一些邊使得這k個點通過選擇的邊聯通,目標是使得選擇的邊數最少。
現需要計算對於所有選擇k個點的情況最小選擇邊數的總和爲多少。
1<=k<=n<=100000
【解題思路】
因爲這道題要求統計所有k個點的選取方案,暴力枚舉肯定不現實,這種情況下一般可以考慮每條邊的貢獻。
樹上的每條邊都是橋,所以當且僅當選取的k個點在某條邊連接的兩個連通塊中都有接點,刪掉這條邊纔是合理而且必要的。考慮補集,當這k個點只存在於在某條邊連接的兩個連通塊中的一箇中,這條邊就不會對答案造成貢獻。
假設這條邊連接的兩個連通塊的大小分別爲siz和N-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;
}