组合数学

记号:\(a/b\) 表示 \(\left [ \dfrac{a}{b}\right ]\)

生成函数

使用母函数的方法求下列数列的通项 \(a_n.\)

\((1) a_0=2,a_1=5,a_{n+2}=3a_{n+1}-2a_n(n=0,1,2,\cdots);\)

: 设 \(f(x)=a_0+a_1x+a_2x^2+a_3x^3+\cdots.\) 则:

\(\qquad-3fx(x)=-3a_0x-3a_1x^2-3a_2x^3-\cdots.\)

\(\quad\quad\qquad\qquad2x^2f(x)=+2a_0x^2+2a_1x^3+2a_2x^4-\cdots.\)

三式相加并结合 \(a_0=2,a_1=5,a_{n+2}=3a_{n+1}-2a_n\) 可知:

\(f(x)=(1-3x+2x^2)f(x)=2-x \qquad \qquad \qquad \qquad (*)\)

解出来 \(f(x)=\dfrac{2-x}{1-3x+2x^2}=\dfrac{2-x}{(x-1)(2x-1)}\)

\(f(x)=\dfrac{A}{x-1}+\dfrac{B}{2x-1}=\dfrac{2-x}{(x-1)(2x-1)}\)

\((*)\) 式两边乘 \(x-1\) 后,令 \(x=1\) 得:

\(A=\dfrac{2-x}{2x-1}\Big{|}_{x=1}=1\)

\((*)\) 式两边乘 \(2x-1\) 后,令 \(x=\dfrac{1}{2}\) 得:

\(B=\dfrac{2-x}{x-1}\Big{|}_{x=\dfrac{1}{2}}=-3\)

\[f(x)=\dfrac{1}{x-1}-\dfrac{3}{2x-1}=\sum_{n=1}^\infty\left[3\times2^n-1\right]x^n \]

\(\therefore a_n = 3\times 2^n -1\)

计数原理和计数公式

提出 \(Lucus\) 定理,定理内容如下:

\[C_m^n \equiv C^{n/p} _{m/p} C_{m \bmod p}^{n\bmod p} \pmod p \]

不予证明。

以此原理, \(C_{m \bmod p}^{n\bmod p}\) 可以直接预处理得到答案,而 \(C^{n/p} _{m/p}\) 可以继续 \(Lucus\) 递归。

依此思路,可以有程式了。

ll C(ll m,ll n){ if(n>m) return 0; return fc[m]%p*inv(fc[m-n])%p*inv(fc[n])%p; }
ll lucas(ll m,ll n){ return !n?1:lucas(m/p,n/p)*C(m%p,n%p)%p; }

扩域

提供一个可以处理无理数的黑科技。

定义新的虚数单位 \(i,i=\sqrt{5}.\)

对于 \(fib\) 数列的通项公式:

\[F_n = \dfrac{1}{\sqrt{5}}\left[\left(\dfrac{1+\sqrt{5}}{2}\right)^n-\left(\dfrac{1-\sqrt{5}}{2}\right)^n\right] \]

\(\sqrt{5}\) 视作虚数单位。原式可化为:

\[F_n = \dfrac{1}{i}\left[\left(\dfrac{1+i}{2}\right)^n-\left(\dfrac{1-i}{2}\right)^n\right] \]

需要注意: \(i^2 = \sqrt{5}^2 = 5.\)

\(zz'=(a+bi)(c+di)=ac+(ad+bc)i+bdi^2=ac+5bd+(ad+bc)i\)

答案是其复数部分。

程式
#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
ll p=1e9+7;
struct cplx{
	ll a,b;
	cplx(ll A=0,ll B=0): a(A), b(B) {}
	cplx operator +(const cplx& tt)const{
		ll u=a+tt.a,v=b+tt.b;
		return cplx(u%p,v%p);
	}
	cplx operator -(const cplx& tt)const{
		ll u=a-tt.a+p,v=b-tt.b+p;
		return cplx(u%p,v%p);
	}
	cplx operator *(const cplx& tt)const{
		ll u=a*tt.a+5*b*tt.b,v=a*tt.b+b*tt.a;
		return cplx(u%p,v%p);
	}
}; 
ll qpow(int a,int b){
	ll base=a,res=1;
	while(b){
		if(b&1)  res=res*base%p;
		base=base*base%p, b>>=1;
	}
	return res;
}
cplx Qpow(cplx a,int b){
	cplx base=a,res=cplx(1,0);
	while(b){
		if(b&1)  res=res*base;
		base=base*base, b>>=1;
	}
	return res;
}
signed main(){
	ll n; cin>>n;
	ll inv2=qpow(2,p-2);
	cplx x=cplx(inv2,inv2);
	cplx y=cplx(inv2,p-inv2);
	cplx xx=Qpow(x,n),yy=Qpow(y,n);
	cplx res=xx-yy;
	ll ans=res.b;
	printf("%lld\n",ans%p);
}

P8106 [Cnoi2021] 数学练习

这个题还是怪好推的,除了我把 \(998244353\) 写成 \(99244353\) 之外这就是个弱智题。

显然,如果是成功的话显然要满足 \(S\) 集包含 \(n-|S|\)\(T\) 包含\(n-|T|.\)

剩下 \(n-2\) 个元素,你还要选 \(|S|-1\) 个元素,所以对于 \(|S|=x\)

\[\sum_{i=1}^{n-1} C_{n-2}^{i-1} \]

种方案。

P9823 [ICPC2020 Shanghai R] The Journey of Geor Autumn

推式子。

先考虑不足以通过本题的 \(\mathcal{O(n^2)}\) 式子。

发现从小到大填数并且记录方案可以顺利完成计数,方法如下:

  1. 发现 \(1\) 可以放进 \([1,k]\) 的位置,假设 \(1\) 放在了 \(p_1\) 位置。
  2. 发现 \(2\) 可以放进 \([1,p_1+k]\) 的位置,假设 \(2\) 放在了 \(p_2\) 位置。
  3. 发现 \(3\) 可以放进 \([1,\max\{p_1,p_2\}+k]\) 的位置,假设 \(3\) 放在了 \(p_3\) 位置。
  4. ...

\(f_{i,w}\) 为现在放了 \(i\) 个数,并且下一个数可以放在 \([1,w]\) 的方案数。

那么 \(f_{i,w}\) 可以由填 \(i+1\) 的时候 \(w\) 增长 \(j\ (j\in[1,k])\) 转移过来。

状态转移方程如下:

\[f_{i,w}=\begin{cases}(w-k-i)f_{i+1,w}+\sum_{j=w+1}^{w+k}f_{i+1,\min\{j,n\}} & w\not=n\\ (i-1)f_{i+1,w} & w=n\end{cases} \]

这样前缀和随便处理处理就是 \(\mathcal{O(n^2)}\) 的,\(i\) 那一维可以滚掉,这样空间就是 \(\mathcal{O(n)}\) 的了。

考虑优化。注意到当且仅当 \(i\) 放在 \([w-k+1,w]\) 的时候 \(f_{i,w}\) 才不会从 \(f_{i+1,w}\) 转移过来。

\(g_n\) 表示输入为 \(n\) 时的答案。枚举 \(1\) 放在 \(j\) 位置的情况,发现如果放在 \(j\) 位置,那么答案会是以下两种情况的积:

  1. 在前面的 \(j-1\) 个位置里面乱填数,除 \(1\) 以外一共有 \(n-1\) 个数,有 \(A_{n-1}^{j-1}\) 种情形。
  2. 填完 \(1\) 以后,剩下的 \(n-i\) 个位置转化为一个大小为 \(n-i\) 的子问题。即有 \(g_{n-i}\) 种情形。

所以,\(g_n\) 就是取所有的 \(j\) 的情况的和。

\[g_n=\sum_{j=1}^{\min\{n,k\}} A_{n-1}^{j-1}g_{n-j}=(n-1)!\sum_{j=1}^{\min\{n,k\}}\dfrac{g_{n-j}}{n-j} \]

这个时候前缀和处理一下就好了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e7+10,maxn=1e7;
const ll p=998244353;
ll fc[N],ifc[N],f[N],s[N];
int n,k;
inline ll qp(ll a,ll b=p-2){
	ll r=1;
	while(b){
		if(b&1)r=(r*a)%p;
		a=(a*a)%p, b>>=1;
	}
	return r;
}
int main(){
	cin>>n>>k,fc[0]=1;
	for(int i=1;i<=maxn;++i)fc[i]=fc[i-1]*i%p;
	ifc[maxn]=qp(fc[maxn]);
	for(int i=maxn-1;~i;--i)ifc[i]=ifc[i+1]*(i+1)%p;
	f[0]=s[0]=1;
	for(int i=1;i<=n;++i){
		f[i]=fc[i-1]*(s[i-1]-(i-min(i,k)-1>=0?s[i-min(i,k)-1]:0))%p;
		s[i]=(s[i-1]+f[i]*ifc[i])%p;
	}
	return cout<<(f[n]%p+p)%p<<"\n",0;
}
posted @ 2024-04-09 23:18  ChihiroFujisaki  阅读(40)  评论(0)    收藏  举报