组合数学
记号:\(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\)
\(\therefore a_n = 3\times 2^n -1\)
计数原理和计数公式
提出 \(Lucus\) 定理,定理内容如下:
不予证明。
以此原理, \(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\) 数列的通项公式:
将 \(\sqrt{5}\) 视作虚数单位。原式可化为:
需要注意: \(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\) 有
种方案。
P9823 [ICPC2020 Shanghai R] The Journey of Geor Autumn
推式子。
先考虑不足以通过本题的 \(\mathcal{O(n^2)}\) 式子。
发现从小到大填数并且记录方案可以顺利完成计数,方法如下:
- 发现 \(1\) 可以放进 \([1,k]\) 的位置,假设 \(1\) 放在了 \(p_1\) 位置。
- 发现 \(2\) 可以放进 \([1,p_1+k]\) 的位置,假设 \(2\) 放在了 \(p_2\) 位置。
- 发现 \(3\) 可以放进 \([1,\max\{p_1,p_2\}+k]\) 的位置,假设 \(3\) 放在了 \(p_3\) 位置。
- ...
设 \(f_{i,w}\) 为现在放了 \(i\) 个数,并且下一个数可以放在 \([1,w]\) 的方案数。
那么 \(f_{i,w}\) 可以由填 \(i+1\) 的时候 \(w\) 增长 \(j\ (j\in[1,k])\) 转移过来。
状态转移方程如下:
这样前缀和随便处理处理就是 \(\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\) 位置,那么答案会是以下两种情况的积:
- 在前面的 \(j-1\) 个位置里面乱填数,除 \(1\) 以外一共有 \(n-1\) 个数,有 \(A_{n-1}^{j-1}\) 种情形。
- 填完 \(1\) 以后,剩下的 \(n-i\) 个位置转化为一个大小为 \(n-i\) 的子问题。即有 \(g_{n-i}\) 种情形。
所以,\(g_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;
}

浙公网安备 33010602011771号