一道不错的分治ntt题目
题目稍微转化一下,就是所有k条链的存在交,并且交的部分都被覆盖k次
所以一定是两个点,之间路径选择k次,然后端点两开花
f[x]表示x子树内往下延伸k条链(可以停在x)的方案数(有标号)
每个子树选择一个或者不选择,最多一共选择k个,dp是O(n^2)的,
考虑生成函数,其实就是:
而
统计答案?
直接两两点对f相乘肯定不行,因为f仅仅是子树
考虑枚举x作为lca统计
如果是拐弯的链,树形DP即可。
而如果是直上直下的链,
对于不同子树,x的选择是扣去这个子树,还可以往上选择
分治NTT的经典问题
const int N=1e5+5; int n,k; int jie[N],inv[N]; int A(int n,int m){ if(n<0||m<0||n<m) return 0; return mul(jie[n],inv[n-m]); } int val[N],si[N]; void divi(int l,int r,Poly &f,Poly &g){ // cout<<" divi "<<l<<" "<<r<<endl; if(l==r){ g.resize(1);g[0]=val[l]; f.resize(2);f[0]=1;f[1]=si[l]; return; } Poly lf,lg,rf,rg; int mid=(l+r)>>1; divi(l,mid,lf,lg); divi(mid+1,r,rf,rg); f=lf*rf; g=(lf*rg)+(lg*rf); } int f[N],sum[N],son[N]; int ans,sz[N]; struct node{ int nxt,to; }e[2*N]; int hd[N],cnt; void add(int x,int y){ e[++cnt].nxt=hd[x]; e[cnt].to=y; hd[x]=cnt; } void dfs(int x,int fa){ // cout<<" xx "<<x<<" fa "<<fa<<endl; sz[x]=1; int pre=0; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; ++son[x]; dfs(y,x); sz[x]+=sz[y]; sum[x]=ad(sum[x],sum[y]); ans=ad(ans,mul(pre,sum[y])); pre=ad(pre,sum[y]); } if(son[x]){ Poly F,G; int ct=0; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; val[++ct]=sum[y]; si[ct]=sz[y]; } divi(1,ct,F,G); Poly T; T.resize(2); T[0]=1;T[1]=n-sz[x]; G=G*T; for(reg i=0;i<=min(k,son[x]);++i){ // cout<<"F["<<i<<"] "<<F[i]<<" G["<<i<<"] "<<G[i]<<endl; ans=ad(ans,mul(A(k,i),G[i])); f[x]=ad(f[x],mul(A(k,i),F[i])); } sum[x]=ad(sum[x],f[x]); }else{ f[x]=1;sum[x]=1; } // cout<<" return "<<x<<" f "<<f[x]<<endl; } int main(){ rd(n);rd(k); if(k==1){ ans=(ll)n*(n-1)/2%mod; ot(ans);return 0; } jie[0]=1; for(reg i=1;i<=k;++i) jie[i]=mul(jie[i-1],i); inv[k]=qm(jie[k],mod-2); for(reg i=k-1;i>=0;--i) inv[i]=mul(inv[i+1],i+1); int x,y; for(reg i=1;i<n;++i){ rd(x);rd(y);add(x,y);add(y,x); } dfs(1,0); ot(ans); return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/4/8 18:57:00 */