【x义x讲坛】生成函数入门
声明:该博客使用的例题大多来自《信息学奥赛数学一本通》。
众所周知:
然后,你可能在高中作业里做到让你求
扩展一下,我们可以用一个函数来代表一个数列,然后把对数列的乱搞变成对这个函数的代数运算,比如加法,减法,乘法,求导,积分之类的。于是可以随便选一批“标志函数”
是数列
§ 1 § 普通型母函数
普通型母函数的特色是标志函数
§ 1.1 § 一些有特色的数列和它们的普通型母函数
考虑一个非常sb的数列
显然,
两个减一下得到
这个结果在某种意义上没有问题,因为如果你把一个
但是,一般来讲,在做题的时候你是不用考虑这些的,因为我们主要想要的是
再列出一些:
§ 1.2 § 普通型母函数的乘法
普通型母函数的乘法和“多重集的组合问题”有关。不懂什么是“多重集的组合问题”没关系,我也不懂。我们看一道例题了解一下吧。
【例一】
现在有重量为1g的砝码两个,3g的砝码两个,5g的砝码一个。求:
-
能称出多少种重量?(0g也算一种)
-
有多少种方式称出重量为6g?
【例一解答】
先放结论:
的展开式中,项数即为第一小问,
为什么?
这可以看做是一个动态规划问题,设当前称出
其他题目也有类似的思想,此处不表。
§ 1.3 § 其他
有时候,普通型母函数只是刚好“凑到”了某个数列的递推式而已。
【例二】
求在十进制下,
【例二解答】
设答案为
对于一个
-
一个
n-1 位的,有偶数个5的数的末尾添了一个非5数。 -
一个
n-1 位的,有奇数个5的数的末尾添了一个5。
也就是说,
对称地,
如果我们构造
然后一顿解,解得
然而这玩意解出来看似毛用没有,因为我们实际上需要的是
观察一下
于是,
类似地:
【例三】
求斐波那契数列:
的通项公式。
【例三解答】
类似上面的方法,设
类似地,我们也要把这玩意化开。设:
则易解得
然后就很简单了吧。
你可能听过斐波那契数列通项公式的特征方程解法,反正当时我听这个方法的时候认为这简直就是玄学,这两天才终于发现这个所谓特征方程方法就是生成函数里的一个小结论……
另外,容易发现把一个数列的普通型母函数乘以所以,你应该已经可以独立完成P5488了。不就是加个NTT吗,有什么难的!
P5488代码如下:
#include<bits/stdc++.h>
using namespace std;
const int p=1004535809,Len=262144;
const int g=3,ig=334845270;
int read(){
int num=0,neg=1;char c=getchar();
while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
while(isdigit(c)) num=num*10+c-'0',c=getchar();
return num*neg;
}
int read_p(){
int num=0;bool neg=0;char c=getchar();
while(!isdigit(c)){if(c=='-') neg=1;c=getchar();}
while(isdigit(c)) num=(1LL*num*10%p+c-'0')%p,c=getchar();
return neg?(p-num):num;
}
int inv[300005];
int qpow(int a,int k){
int ans=1;
while(k){
if(k&1) ans=1LL*ans*a%p;
a=1LL*a*a%p;
k>>=1;
}
return ans;
}
int N,K,X=1,len;bool flg;
int A[300005];
int B[300005];
int R[300005];
void NTT(int a[],bool flg){
for(int i=0;i<X;i++)
if(i<R[i]) swap(a[i],a[R[i]]);
for(int mid=1;mid<X;mid<<=1){
int Wn=qpow((flg?ig:g),(p-1)/(mid<<1));
for(int j=0;j<X;j+=(mid<<1)){
int w=1;
for(int k=0;k<mid;k++){
int a0=a[j+k],a1=1LL*w*a[j+mid+k]%p;
a[j+k]=(a0+a1)%p;a[j+mid+k]=(a0-a1+p)%p;
w=1LL*w*Wn%p;
}
}
}
}
int main(){
inv[1]=1;
for(int i=2;i<=Len;i++)
inv[i]=1LL*(p-p/i)*inv[p%i]%p;
N=read(),K=read_p(),flg=read();
while(X<(N<<1)) X<<=1,len++;
for(int i=0;i<X;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(len-1));
for(int i=0;i<N;i++)
A[i]=read();
B[0]=1;
if(flg){
for(int i=1;i<N;i++)
B[i]=1LL*B[i-1]*(K-i+1+p)%p*inv[i]%p;
for(int i=1;i<N;i+=2)
B[i]=p-B[i];
}
else{
for(int i=1;i<N;i++)
B[i]=1LL*B[i-1]*(i+K-1)%p*inv[i]%p;
}
NTT(A,0);NTT(B,0);
for(int i=0;i<X;i++) A[i]=1LL*A[i]*B[i]%p;
NTT(A,1);
int d=qpow(X,p-2);
for(int i=0;i<N;i++)
printf("%d ",1LL*A[i]*d%p);
}
§ 2 § 指数型母函数
指数型母函数的标志函数是
§ 2.1 § 一些有特色的数列和它们的指数型母函数
仍然考虑sb数列
对
又由于
再列出一些。
§ 2.2 § 指数型母函数的乘法
指数型母函数的乘法和“多重集的排列问题”有关。
【例四】
用两个1,两个3,一个5:
- 能排成多少个不同的四位数?
【例四解答】
展开
一点点简单的排列组合知识即可解释,于是不再赘述。
【例五】
求由ACTG组成的
【例五解答】
A和C可以用之前的
括号拆成
还是还原回sigma的形式:
于是
【例六】
UVA1305 Chocolate
【例六解答】
由于每一个抽取排列都是等概率的,所以我们把抽
把问题转化一下:其实就是
然后二项式定理展开一下,预处理一下组合数就可以做了。
不幸的是作者代码的精度死活卡不过去所以并没有(在UVA上)AC……(POJ上倒是A了)
给出不能AC的代码:
#include<bits/stdc++.h>
#define db long double
using namespace std;
int C,N,M;
long long CC[105][105];
db ANS[105][105];
db ans;
double qpow(double a,int k){
double tmp=1;
while(k){
if(k&1) tmp=tmp*a;
a=a*a;
k>>=1;
}
return tmp;
}
int main(){
freopen("choc.in","r",stdin);
freopen("choc.out","w",stdout);
CC[0][0]=1;
for(int i=1;i<=100;i++){
CC[i][0]=1;
for(int j=1;j<=i;j++)
CC[i][j]=CC[i-1][j-1]+CC[i-1][j];
}
scanf("%d",&C);
while(C){
scanf("%d%d",&N,&M);
if(N==0&&M==0){printf("1.000\n");scanf("%d",&C);continue;}
if(M>C||M>N){printf("0.000\n");scanf("%d",&C);continue;}
ans=0.0;
for(int k=0;k<=C-M;k++)
for(int i=0;i<=M;i++)
if(i&1) ans-=qpow(1-2.0*(db)(k+i)/C,N)*CC[C-M][k]*CC[M][i];
else ans+=qpow(1-2.0*(db)(k+i)/C,N)*CC[C-M][k]*CC[M][i];
printf("%.3Lf\n",fabs(CC[C][M]/qpow(2,C)*ans));
scanf("%d",&C);
}
return 0;
}
§ 3 § 小结
于是在作者能力范畴内的内容就讲完了……wtcl
由于这些母函数很多时候都是多项式,所以经常和多项式那一堆板子扯上关系,而作者连FFT都不会……
先写到这里算了。
UPD:更多内容会在生成函数再入门更新。