# CF1691F K-Set Tree 题解
Solution 7 / 11
4 min read
Table of Contents
提供一种不用 dp 的做法,跑得飞快。
题意
给定一棵有 个点的树及正整数 ,定义 为以 为根时包含集合 中所有点的最小子树的大小,求 。
思路
先考虑对于一个给定的集合 ,有没有什么简单的式子可以表示。
我们称这样的 为关键点:,或者以 为根时,至少有两棵子树里有 的点。显然若 为关键点,则 。如在样例 2 中,若 ,则点 为关键点。
对于关键点之外的点,设它们形成了 个连通块,第 个连通块大小为 。若 在第 个连通块中,则 。
考虑所有点的 值之和:
- 共有 个关键点,每个关键点的 值为 ,共为 ;
- 第 个连通块有 个点,每个点的 值为 ,共为 ;
- 所有点的 值之和为 。
故问题转化为求每种 下的 之和。发现连通块一共只有 个(每条树边两侧的连通块),考虑每个连通块的贡献。
先计算以 为根的子树作为连通块的次数。对于一种 ,这棵子树被作为连通块当且仅当 是关键点。记 为以 为根时 子树的大小。此时有两种情况:
- :此时共有 种情况;
- :此时共有 种情况,即在去掉 子树和 后的点中取 个,然后去掉 不是关键点的情况。
以 「全部点去掉 子树」作为连通块的次数与上同理。
然后即可算出答案。
注意:可以先预处理出 ,否则在前一种情况中多次访问父亲的儿子,会被菊花图卡掉。
代码
#include <bits/stdc++.h>using namespace std;typedef long long ll;const ll mod=1e9+7;const int N=2e5+5;ll ksm(ll u,ll v=mod-2){ll tmp=1;u%=mod;while(v) tmp=tmp*((v&1)?u:1)%mod,u=u*u%mod,v>>=1;return tmp;}ll jie[N],inv[N];void init(int n=N-3){ jie[0]=1; for(int i=1;i<=n;i++) jie[i]=1ll*jie[i-1]*i%mod; inv[n]=ksm(jie[n],mod-2); for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;}ll C(ll u,ll v){ if(v<0||v>u) return 0; return 1ll*jie[u]*inv[v]%mod*inv[u-v]%mod;}//---------------------------------------------预处理组合数int head[N],tot;struct EDGE{ int nex,to;} e[N<<1];void add(int u,int v){ e[++tot].nex=head[u],e[tot].to=v; head[u]=tot;}ll n,k,SZ[N],fa[N],p[N];ll ans=0;void dfs(int u,int fat){ //预处理 SZ,p,fa 数组 SZ[u]=1,fa[u]=fat; for(int i=head[u];i;i=e[i].nex){ int v=e[i].to; if(v==fat) continue; dfs(v,u); SZ[u]+=SZ[v]; p[u]=(p[u]+C(SZ[v],k))%mod; }}int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); init(); cin>>n>>k; for(int i=1,u,v;i<n;i++){ cin>>u>>v; add(u,v),add(v,u); } dfs(1,0); ll num; for(int u=2;u<=n;u++){ num=C(n-SZ[u]-1,k-1)+C(n-SZ[u]-1,k)-(p[fa[u]]-C(SZ[u],k))-C(n-SZ[fa[u]],k); num%=mod; ans=(ans+SZ[u]*SZ[u]%mod*num)%mod; //第一种连通块
num=C(SZ[u]-1,k-1)+C(SZ[u]-1,k)-p[u]; num%=mod; ans=(ans+(n-SZ[u])*(n-SZ[u])%mod*num)%mod; //第二种连通块 } cout<<(n*n%mod*C(n,k)%mod-ans+mod)%mod<<"\n"; //计算答案 return 0;}