【AGC005 F】Many Easy Problems

神他吗一天考一道码农题两道 FFT(其实还是我推式子一窍不通)

题意

  给你一棵 \(n\) 个点的树,再给你一个常数 \(k\)
  设 \(S\) 为树上某些点的集合,定义 \(f(S)\) 为最小的包含 \(S\) 的连通子图的大小。
  \(n\) 个点选 \(k\) 个点一共有 \(\tbinom{n}{k}\) 种方案,请求出所有方案的 \(f(S)\) 之和。
  出题人觉得这样就太简单了,他决定让你求出 \(k=1\cdots n\) 的答案。
  对于 \(27\%\) 的数据,\(n\le 2700\)
  对于 \(100\%\) 的数据,$ n\le 2\times 10^5$

题解

  这种题的关键点在于 \(n^2\) \(\text{dp}\)
  首先无法对于每个 \(k\) 快速求出答案,但我们可以考虑一个点对每个 \(k\) 的贡献。

  把 \(x\) 转为整棵树的根,则一个点 \(x\) 在这个连通子图内,当且仅当这 \(k\) 个点不都在 \(x\) 的某一个儿子子树里。

  那么贡献为 \(\tbinom{n}{k}-\sum\tbinom{a_i}{k}\),其中 \(a_i\) 为以 \(x\) 为根时,各个儿子子树的大小。
  不难发现,计算总的贡献时,\(\tbinom{n}{k}\) 会被计算 \(n\) 次,每条边两端的子树大小都会被计算一次。
  设 \(num_i\) 表示大小为 \(i\) 的子树被计算的次数(不管加还是减),则 $$b_i = \begin{cases} n\space\space\space\space\space\space\space\space\space\space\space (i=n) \ -num_i\space (i≠n) \end{cases}$$
  其中 \(cnt_i\) 为大小为 \(i\) 的子树个数。
  由于一棵子树被且仅被被计算一次,所以 $$\begin{align} ans_k&= \sum\limits_{i\ge k} \tbinom{i}{k} b_i \nonumber \ &= \sum\limits_{i\ge k} \frac{i!}{(i-k)!k!} b_i \nonumber \ &= \frac{1}{k!} \sum\limits_{i\ge k} b_i i!\times \frac{1}{(i-k)!} \nonumber \end{align}$$
  此时直接计算是 \(O(n^2)\) 的,可以得到 \(27\) 分。

  我们发现 \(ans\) 像一个可以卷积的生成函数。
  又发现 \(\sum\limits_{i\ge k} b_i i!\times \frac{1}{(i-k)!} = [b_k k! \frac{1}{0!}] + [b_{k+1} (k+1)! \frac{1}{1!}] \cdots + [b_n n! \frac{1}{(n-k)!}]\)
  故我们需要构造一个序列 \(c\),使得 \(c_{0\cdots (n-k)}\) 包含了 \(b_k k!,\space b_{k+1} (k+1)!,\space\cdots ,\space b_n n!\);再构造一个序列 \(d\),使得 \(d_{0\cdots (n-k)}\) 包含了 \(\frac{1}{0!},\space \frac{1}{1!},\space \cdots,\space \frac{1}{(n-k)!}\)。我们还要使一个序列的 \(i\) 由大变小,另一个序列的 \(i\) 由小变大,这样才卷积时才会把原序列中相同位置乘起来。
  于是不难构造出 $$\begin{align} c_{n-i} &= b_i i! \nonumber \ d_i &= \frac{1}{i!} \nonumber \ a_i &= \sum\limits_{j+k=i} c_j d_k \nonumber \ ans_i &= \frac{a_{n-i}}{i} \nonumber \end{align}$$
  \(\text{NTT}\) 计算出序列 \(a\) 即可。
  复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define ll long long
#define N 800010
#define mod 924844033
#define G 5
#define invG 554906420
using namespace std;
inline int read(){
	int x=0; bool f=1; char c=getchar();
	for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
	for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
	if(f) return x;
	return 0-x;
}
int n;
struct edge{int v,nxt;}e[N<<1];
int hd[N],cnt;
inline void add(int u, int v){e[++cnt]=(edge){v,hd[u]}, hd[u]=cnt;}
int Pow(int x, int y){
	int ret=1;
	while(y){
		if(y&1) ret=(ll)ret*x%mod;
		x=(ll)x*x%mod;
		y>>=1;
	}
	return ret;
}
int siz[N],num[N];
void dfs(int u, int fa){
	siz[u]=1;
	for(int i=hd[u]; i; i=e[i].nxt) if(e[i].v!=fa){
		dfs(e[i].v,u);
		siz[u]+=siz[e[i].v];
		--num[siz[e[i].v]], --num[n-siz[e[i].v]];
	}
}
int jc[N],jcn[N];
int a[N],b[N],c[N];
struct Poly{
	int n,bit,r[N];
	void init(int x){
		for(n=1, bit=0; n<=x; n<<=1, ++bit);
		for(int i=1; i<n; ++i) r[i] = (r[i>>1]>>1) | ((i&1)<<(bit-1));
	}
	void dft(int *a, int f){
		for(int i=0; i<n; ++i) if(i<r[i]) swap(a[i],a[r[i]]);
		for(int i=1; i<n; i<<=1){
			int wn = Pow(f==1 ? G : invG, (mod-1)/(i<<1));
			for(int j=0; j<n; j+=(i<<1)){
				int w=1, x, y;
				for(int k=0; k<i; ++k, w=(ll)w*wn%mod)
					x=a[j+k], y=(ll)w*a[j+i+k]%mod,
					a[j+k]=(x+y)%mod, a[j+i+k]=(x-y+mod)%mod;
			}
		}
		if(f==-1){
			int tmp=Pow(n,mod-2);
			for(int i=0; i<n; ++i) a[i]=(ll)a[i]*tmp%mod;
		}
	}
}NTT;
int main(){
	n=read();
	int u,v;
	for(int i=1; i<n; ++i) u=read(), v=read(), add(u,v), add(v,u);
	jc[0]=1;
	for(int i=1; i<=n; ++i) jc[i]=(ll)jc[i-1]*i%mod;
	jcn[n]=Pow(jc[n],mod-2);
	for(int i=n-1; i>=0; --i) jcn[i]=(ll)jcn[i+1]*(i+1)%mod;
	dfs(1,0);
	num[n]=n;
	for(int i=0; i<=n; ++i)
		b[i]=((ll)num[n-i]*jc[n-i]%mod+mod)%mod,
		c[i]=jcn[i];
	NTT.init(n*2+1), NTT.dft(b,1), NTT.dft(c,1);
	for(int i=0; i<NTT.n; ++i) a[i]=(ll)b[i]*c[i]%mod;
	NTT.dft(a,-1);
	for(int i=1; i<=n; ++i) printf("%lld\n",(ll)a[n-i]*jcn[i]%mod);
	return 0;
}
posted @ 2019-09-11 15:28  大本营  阅读(147)  评论(0编辑  收藏  举报