【洛谷P5825 排列计数】【生成函数+二项式反演/欧拉数】

题意

我们记一个排列 P P P的升高为 k k k当且仅当存在 k k k个位置 i i i使得 P i < P i + 1 P_i < P_{i+1} Pi<Pi+1
现在给定排列长度 n n n,对于所有整数 k ∈ [ 0 , n ] k\in [0,n] k[0,n],求有多少个排列的升高为 k k k
n ≤ 200000 n\le 200000 n200000

做法一

考虑钦定 k k k个位置为 < < <,其他位置不管。对 ( n − 1 k ) \binom{n-1}{k} (kn1)种不同取法的方案数求和,记为 g k g_k gk
如果把关系为 < < <的相邻两个位置合并,则共有 n − k n-k nk个集合,记第 i i i个集合的大小为 s i s_i si,则合法的排列数量为 n ! ∏ s i ! \frac{n!}{\prod s_i!} si!n!

因此 g k = ∑ s i n ! ∏ s i ! g_k=\sum_{s_i}\frac{n!}{\prod s_i!} gk=sisi!n!

写成生成函数的形式,就是 g k = n ! [ x n ] ( e x − 1 ) m = n ! [ x n ] ∑ i = 0 m ( m i ) ( − 1 ) m − i e i x = n ! ∑ i = 0 m ( m i ) ( − 1 ) m − i i n i ! \begin{aligned} g_k&=n! [x^n] (e^x-1)^m\\ &=n! [x^n] \sum_{i=0}^m\binom{m}{i}(-1)^{m-i}e^{ix}\\ &=n! \sum_{i=0}^m\binom{m}{i}(-1)^{m-i}\frac{i^n}{i!} \end{aligned} gk=n![xn](ex1)m=n![xn]i=0m(im)(1)mieix=n!i=0m(im)(1)mii!in

把组合数展开然后FFT就可以求出 g k g_k gk

f k f_k fk表示答案,不难发现 g i = ∑ j = i n − 1 ( j i ) f j g_i=\sum_{j=i}^{n-1}\binom{j}{i}f_j gi=j=in1(ij)fj

二项式反演一下就可以得到 f i = ∑ j = i n − 1 ( j i ) ( − 1 ) j − i g j f_i=\sum_{j=i}^{n-1}\binom{j}{i}(-1)^{j-i}g_j fi=j=in1(ij)(1)jigj

再做一次FFT即可。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

做法二

f n , m f_{n,m} fn,m表示长度为 n n n,恰好有 m m m < < <的排列数, f n , m f_{n,m} fn,m也称欧拉数。

不难得到递推公式 f n , k = ( k + 1 ) f n − 1 , k + ( n − k ) f n − 1 , k − 1 f_{n,k}=(k+1)f_{n-1,k}+(n-k)f_{n-1,k-1} fn,k=(k+1)fn1,k+(nk)fn1,k1

欧拉数还有如下通项 f n , m = ∑ k = 0 m ( n + 1 k ) ( − 1 ) k ( m + 1 − k ) n f_{n,m}=\sum_{k=0}^m \binom{n+1}{k}(-1)^k(m+1-k)^n fn,m=k=0m(kn+1)(1)k(m+1k)n

可以通过数学归纳法证明上述通项

f n , m = ( m + 1 ) f n − 1 , m + ( n − m ) f n − 1 , m − 1 f_{n,m}=(m+1)f_{n-1,m}+(n-m)f_{n-1,m-1} fn,m=(m+1)fn1,m+(nm)fn1,m1

= ( m + 1 ) ∑ k = 0 m ( n k ) ( − 1 ) k ( m + 1 − k ) n − 1 + ( n − m ) ∑ k = 0 m − 1 ( n k ) ( − 1 ) k ( m − k ) n − 1 =(m+1)\sum_{k=0}^m\binom{n}{k}(-1)^k(m+1-k)^{n-1}+(n-m)\sum_{k=0}^{m-1}\binom{n}{k}(-1)^k(m-k)^{n-1} =(m+1)k=0m(kn)(1)k(m+1k)n1+(nm)k=0m1(kn)(1)k(mk)n1

= ∑ k = 0 m ( n k ) ( − 1 ) k [ ( m + 1 ) ( m + 1 − k ) n − 1 + ( n − m ) ( m − k ) n − 1 ] =\sum_{k=0}^m\binom{n}{k}(-1)^k[(m+1)(m+1-k)^{n-1}+(n-m)(m-k)^{n-1}] =k=0m(kn)(1)k[(m+1)(m+1k)n1+(nm)(mk)n1]

= ∑ k = 0 m ( n k ) ( − 1 ) k [ ( m + 1 − k ) n + k ( m + 1 − k ) n − 1 + ( n − m ) ( m − k ) n − 1 ] =\sum_{k=0}^{m}\binom{n}{k}(-1)^k[(m+1-k)^n+k(m+1-k)^{n-1}+(n-m)(m-k)^{n-1}] =k=0m(kn)(1)k[(m+1k)n+k(m+1k)n1+(nm)(mk)n1]

展开后最左边那项就是我们想要的 ∑ k = 0 m ( n + 1 k ) ( − 1 ) k ( m + 1 − k ) n \sum_{k=0}^m\binom{n+1}{k}(-1)^k(m+1-k)^n k=0m(kn+1)(1)k(m+1k)n

考虑后面两项

∑ k = 0 m ( n k ) ( − 1 ) k ( n − m ) ( m − k ) n − 1 + ∑ k = 0 m ( n k ) ( − 1 ) k ( m + 1 − k ) n [ k m + 1 − k − k n + 1 − k ] \sum_{k=0}^m\binom{n}{k}(-1)^k(n-m)(m-k)^{n-1}+\sum_{k=0}^m\binom{n}{k}(-1)^k(m+1-k)^n\Big[\frac{k}{m+1-k}-\frac{k}{n+1-k}\Big] k=0m(kn)(1)k(nm)(mk)n1+k=0m(kn)(1)k(m+1k)n[m+1kkn+1kk]

= ( n − m ) ∑ k = 0 m ( n k ) ( − 1 ) k ( m − k ) n − 1 + ∑ k = 0 m ( n k ) ( − 1 ) k ( m + 1 − k ) n k n − m ( m + 1 − k ) ( n + 1 − k ) =(n-m)\sum_{k=0}^m\binom{n}{k}(-1)^k(m-k)^{n-1}+\sum_{k=0}^m\binom{n}{k}(-1)^k(m+1-k)^nk\frac{n-m}{(m+1-k)(n+1-k)} =(nm)k=0m(kn)(1)k(mk)n1+k=0m(kn)(1)k(m+1k)nk(m+1k)(n+1k)nm

前面那项当 k = m k=m k=m时贡献为 0 0 0,用 k − 1 k-1 k1替换 k k k,可以得到上式等于

( n − m ) ∑ k = 1 m ( n k − 1 ) ( − 1 ) k − 1 ( m − k + 1 ) n − 1 + ( n − m ) ∑ k = 0 m ( n k ) ( − 1 ) k ( m + 1 − k ) n − 1 k n + 1 − k (n-m)\sum_{k=1}^m\binom{n}{k-1}(-1)^{k-1}(m-k+1)^{n-1}+(n-m)\sum_{k=0}^m\binom{n}{k}(-1)^k(m+1-k)^{n-1}\frac{k}{n+1-k} (nm)k=1m(k1n)(1)k1(mk+1)n1+(nm)k=0m(kn)(1)k(m+1k)n1n+1kk

不难发现上式就等于 0 0 0

发现是个卷积形式,直接FFT就好了。

代码(做法一)

#include

typedef long long LL;

const int N=200005;
const int MOD=998244353;

int n,jc[N],ny[N],g[N],a[N*4],b[N*4],rev[N*4],L;

void pre(int n)
{
	int lg=0;
	for (L=1;L<=n;L<<=1,lg++);
	for (int i=0;i<L;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
}

int ksm(int x,int y)
{
	int ans=1;
	while (y)
	{
		if (y&1) ans=(LL)ans*x%MOD;
		x=(LL)x*x%MOD;y>>=1;
	}
	return ans;
}

void NTT(int *a,int f)
{
	for (int i=0;i<L;i++) if (i<rev[i]) std::swap(a[i],a[rev[i]]);
	for (int i=1;i<L;i<<=1)
	{
		int wn=ksm(3,f==1?(MOD-1)/i/2:MOD-1-(MOD-1)/i/2);
		for (int j=0;j<L;j+=(i<<1))
		{
			int w=1;
			for (int k=0;k<i;k++)
			{
				int u=a[j+k],v=(LL)a[j+k+i]*w%MOD;
				a[j+k]=(u+v)%MOD;a[j+k+i]=(u+MOD-v)%MOD;
				w=(LL)w*wn%MOD;
			}
		}
	}
	if (f==-1) for (int i=0,inv=ksm(L,MOD-2);i<L;i++) a[i]=(LL)a[i]*inv%MOD;
}

int main()
{
	scanf("%d",&n);
	jc[0]=jc[1]=ny[0]=ny[1]=1;
	for (int i=2;i<=n;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=(LL)(MOD-MOD/i)*ny[MOD%i]%MOD;
	for (int i=2;i<=n;i++) ny[i]=(LL)ny[i-1]*ny[i]%MOD;
	for (int i=0;i<=n;i++) a[i]=(LL)ksm(i,n)*ny[i]%MOD,b[i]=(i&1)?MOD-ny[i]:ny[i];
	pre(n*2);
	NTT(a,1);NTT(b,1);
	for (int i=0;i<L;i++) a[i]=(LL)a[i]*b[i]%MOD;
	NTT(a,-1);
	for (int i=0;i<n;i++) g[i]=(LL)jc[n-i]*a[n-i]%MOD;
	for (int i=0;i<L;i++) a[i]=b[i]=0;
	for (int i=0;i<=n;i++) a[n-i]=(i&1)?MOD-ny[i]:ny[i],b[i]=(LL)g[i]*jc[i]%MOD;
	NTT(a,1);NTT(b,1);
	for (int i=0;i<L;i++) a[i]=(LL)a[i]*b[i]%MOD;
	NTT(a,-1);
	for (int i=0;i<n;i++) printf("%d ",(LL)a[i+n]*ny[i]%MOD);
	puts("0");
	return 0;
}

代码(做法二)

#include

typedef long long LL;

const int N=200005;
const int MOD=998244353;

int n,jc[N],ny[N],g[N],a[N*4],b[N*4],rev[N*4],L;

void pre(int n)
{
	int lg=0;
	for (L=1;L<=n;L<<=1,lg++);
	for (int i=0;i<L;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
}

int ksm(int x,int y)
{
	int ans=1;
	while (y)
	{
		if (y&1) ans=(LL)ans*x%MOD;
		x=(LL)x*x%MOD;y>>=1;
	}
	return ans;
}

void NTT(int *a,int f)
{
	for (int i=0;i<L;i++) if (i<rev[i]) std::swap(a[i],a[rev[i]]);
	for (int i=1;i<L;i<<=1)
	{
		int wn=ksm(3,f==1?(MOD-1)/i/2:MOD-1-(MOD-1)/i/2);
		for (int j=0;j<L;j+=(i<<1))
		{
			int w=1;
			for (int k=0;k<i;k++)
			{
				int u=a[j+k],v=(LL)a[j+k+i]*w%MOD;
				a[j+k]=(u+v)%MOD;a[j+k+i]=(u+MOD-v)%MOD;
				w=(LL)w*wn%MOD;
			}
		}
	}
	if (f==-1) for (int i=0,inv=ksm(L,MOD-2);i<L;i++) a[i]=(LL)a[i]*inv%MOD;
}

int main()
{
	scanf("%d",&n);
	jc[0]=jc[1]=ny[0]=ny[1]=1;
	for (int i=2;i<=n+1;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=(LL)(MOD-MOD/i)*ny[MOD%i]%MOD;
	for (int i=2;i<=n+1;i++) ny[i]=(LL)ny[i-1]*ny[i]%MOD;
	for (int i=0;i<=n+1;i++) a[i]=ksm(i+1,n),b[i]=(LL)jc[n+1]*ny[i]%MOD*ny[n+1-i]%MOD,b[i]=(i&1)?MOD-b[i]:b[i];
	pre(n*2);
	NTT(a,1);NTT(b,1);
	for (int i=0;i<L;i++) a[i]=(LL)a[i]*b[i]%MOD;
	NTT(a,-1);
	for (int i=0;i<n;i++) printf("%d ",a[i]);
	puts("0");
	return 0;
}

你可能感兴趣的:(组合数学,生成函数)