[WC2019] 数树纯组合线性做法

NaCly_Fish 的博客激发了继续思考的欲望。

我是多项式白痴,所以让我们来思考组合意义做法!

本题本质上是需要让我们求 \(\sum_{E_1 \text{是树}} \sum_{E_2 \text{是树}} y^{-|E1\cup E2|}\) 的值。

我们容斥一下交集,发现考虑上容斥系数就是将 \(y\leftarrow \frac{1}{y}-1\)

剩下部分的方案数考虑扩展凯莱定理,即已经有 \(k\) 个连通块,第 \(i\) 个联通块大小为 \(s_i\),则连城一棵树的方案是 \(n^{k-2}\prod_{i=1}^k s_i\)

\(op=0\) very ez。对于 \(op=1\) 的情况,\(\prod s_i\) 可以转化为每个连通块恰好选一个点的方案数,简单树形 DP 即可。

对于 \(op=2\),我们使用 \(\exp\) 去组合交集中形成的所有连通块。每个连通块成为树的方案数是 \(i^{i-2}\),还有 \(i^2\) 的系数。所以这种情况相当于我们要求一个 \([x^n]\exp(w\sum_{i=0} i^i\frac{x^i}{i!})\)\(w\) 是给定常数。

发现中间有一个 \(i^i\),这令我们回想起 ya 老师在 ZR 模拟赛中投的一个 C,在 \(k\) 很小时用神奇的组合意义乱杀了所有诸如 \((\sum_{i=0} i^{i+k}\frac{x^i}{i!})^m\) 这样的问题。环一周还给出了 \(|k|\) 很小时的扩展。

我们现在在此题继续应用这种神奇组合意义。

\(i^i\) 的组合意义为:生成一个一共 \(i\) 个点的有根森林,假设其中有 \(t\) 棵树,那么带上 \(t!\) 的系数。证明考虑枚举树的个数 \(t\)

\[\sum_{t=1}^{i} {i-1\choose t-1} t! i^{i-t}=i^i \]

那么现在我们考虑如何处理 \(\exp\)。由于 \(\exp F=\sum_{k=0} \frac{F^k}{k!}\),我们考虑求出所有 \([x^n]F^k\)。这个式子的组合意义是我们考虑先用 \(n\) 个点造出 \(d\) 棵树,方案数为 \({n-1\choose d-1}n^{n-d}\)。然后再将其分配给 \(k\) 个组,要求每个组非空,且每个组如果分到了 \(t\) 个就有 \(t!\) 个系数,发现 \(t!\) 正好与多重集组合数系数中的阶乘逆元抵消,方案数就只剩下先随机打乱 \(d\) 个数再插板的 \({d-1\choose k-1}d!\)

由于 \({n-1\choose d-1}{d-1\choose k-1}={n-1\choose k-1}{n-k\choose n-d}\),我们就是要对于每一个 \(k\)\(\sum_{d=k}^{n} {n-k\choose n-d}n^{n-d}d!\)

此时直接卷得到了一个清新的 \(O(n\log n)\) 做法。但我们还可以做得更好!这个式子可以递推求出。

\(f_{s,t}=\sum_{i=0}^s {s\choose i}n^i(n-t-i)!\)。我们考虑复刻错排中的手法,对一个位置及其有限的邻项建立关系式从而解方程建立整式递推关系。

我们可以展开 \({s\choose i}\),得到 \(f_{s,t}=nf_{s-1,t+1}+f_{s-1,t}\),也可以展开阶乘的最后一项 \((n-t-i)\) 得到 \(f_{s,t}=(n-t)f_{s,t+1}-nsf_{s,t+2}\)

题目的要求是对于 \(s=0,1,\dots,n\) 求出 \(f_{s,0}\),我们考察 \(f_{s,0},f_{s,1},f_{s-1,0},f_{s-1,1},f_{s-1,2}\) 这五项,依次设为 \(A,B,C,D,E\),然后用上面的式子得到其中的三个关系式,就可以解出:

\[A=C+nD,B=\frac{A-sD}{n-s} \]

初始化 \(A=n!,B=(n-1)!\) 就可以递推出所有我们需要的项了!推导过程中显然可以把引入的 GF 操作全扬了换成对应的组合意义,所以这个题在考纲内!QwQ

那么如何思考这个方法的拓展呢?环一周的想法一针见血:所有形如 \(\sum_{i=0} i^{i+k}\frac{x^i}{i!}\) 的 EGF 都可以利用 ya 老师那道题的组合意义转化成以 \(\sum_{i=0} i^{i-1}\frac{x^i}{i!}\) 为元的形式。这样,所有建立在这种类型上的多项式操作 \(\exp F,F^k,\ln F,\dots\) 都可以转化成一个对一个低阶多项式/分式做对应操作再点乘上一些东西。(事实上环一周后半部分的写法跟我的写法不甚相同,是基于递推求 \(\exp \frac{wx}{1-x}\) 的,我这种做法的优势可能在可以求出所有 \(F^k\) 处的取值)。而 NaCly_Fish 提出的拉反做法最后得出来的形式与这个结果一模一样。也许在这之中还有我们未曾揭示的理论?

环一周:“现在许多的拉反题都有一个组合意义做法。”而拉反本身也可以用组合意义证明,这启发我们拉反这个工具有可能只是隐藏了背后的深厚的组合意义,发掘这些东西可以使我们对于 GF 的掌握力更高。拉反无用论

所以我这个才学拉反的多项式白痴在这里思考这么多干吗。

#include <set>
#include <vector>
#include <cstdio>
#include <cassert>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=100003,P=998244353;
int read(){
	char c=getchar();int x=0;
	while(c<48||c>57) c=getchar();
	do x=x*10+(c^48),c=getchar();
	while(c>=48&&c<=57);
	return x;
}
int qp(int a,int b=P-2){
	int res=1;
	while(b){
		if(b&1) res=(ll)res*a%P;
		a=(ll)a*a%P;b>>=1;
	}
	return res;
}
int n,w,op;
namespace S0{
	set<int> mp[N];
	void solve(){
		int cnt=n;
		for(int i=1;i<n;++i){
			int u=read(),v=read();
			if(u>v) swap(u,v);
			mp[u].emplace(v);
		}
		for(int i=1;i<n;++i){
			int u=read(),v=read();
			if(u>v) swap(u,v);
			if(mp[u].find(v)!=mp[u].end()) --cnt;
		}
		int res=1;
		while(cnt--) res=(ll)res*w%P;
		printf("%d\n",res);
	}
}
namespace S1{
	int f[N],g[N];
	int hd[N],ver[N<<1],nxt[N<<1],tot;
	void add(int u,int v){nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;}
	void dfs(int u,int fa){
		f[u]=w;g[u]=1;
		for(int i=hd[u];i;i=nxt[i]){
			int v=ver[i];
			if(v==fa) continue;
			dfs(v,u);
			f[u]=((ll)f[u]*(f[v]+g[v])+(ll)g[u]*f[v])%P;
			g[u]=(ll)g[u]*(f[v]+g[v])%P;
		}
	}
	void solve(){
		if(w==1){printf("%d\n",qp(n,n-2));return;}
		int res=(ll)qp(P+1-w,n)*qp(n,P-3)%P;
		w=(ll)w*qp(P+1-w)%P*n%P;
		for(int i=1;i<n;++i){
			int u=read(),v=read();
			add(u,v);add(v,u);
		}
		dfs(1,0);
		res=(ll)res*f[1]%P;
		printf("%d\n",res);
	}
}
namespace S2{
	int fac[N],fiv[N];
	int C(int a,int b){return (ll)fac[a]*fiv[b]%P*fiv[a-b]%P;}
	int A,B,F[N];
	int inv[N];
	void solve(){
		if(w==1){printf("%d\n",qp(n,2*n-4));return;}
		int res=(ll)qp(P+1-w,n)*qp(n,P-5)%P;
		w=(ll)w*qp(P+1-w)%P*n%P*n%P;
		fac[0]=1;
		for(int i=1;i<=n;++i) fac[i]=(ll)fac[i-1]*i%P;
		fiv[n]=qp(fac[n]);
		for(int i=n;i;--i) fiv[i-1]=(ll)fiv[i]*i%P;
		inv[1]=1;
		for(int i=2;i<=n;++i) inv[i]=(ll)inv[P%i]*(P-P/i)%P;
		A=fac[n];B=fac[n-1];
		F[0]=A;
		for(int s=1;s<n;++s){
			int C=A,D=B;
			A=(C+(ll)n*D)%P;
			B=((A-(ll)s*D)%P+P)*inv[n-s]%P;
			F[s]=A;
		}
		int ans=0;
		for(int k=1,pw=1;k<=n;++k){
			pw=(ll)pw*w%P;
			ans=(ans+(ll)fiv[k]*C(n-1,k-1)%P*pw%P*F[n-k])%P;
		}
		printf("%d\n",int((ll)ans*res%P));
	}
}
int main(){
	n=read();w=read();op=read();
	if(op==0) S0::solve();
	if(op==1) S1::solve();
	if(op==2) S2::solve();
	return 0;
}
posted @ 2024-07-03 16:11  yyyyxh  阅读(18)  评论(0编辑  收藏  举报