3.1 普通型生成函数
母函数(生成函数):
生成函数有普通型生成函数和指数型生成函数两种。
形式上,普通型母函数用于解决多重集的组合问题,
指数型母函数用于解决多重集的排列问题。
母函数还可以解决递归数列的通项问题(例如使用母函数解决斐波那契数列,Catalan数的通项公式)。
普通母函数:
构造母函数G(x), G(x) = a0 + a1*x + a2* + a3* +....+ an*, 则称G(x)是数列a0,a1…an的母函数。
通常普通母函数用来解多重集的组合问题,其思想就是构造一个函数来解决问题,一般过程如下:
1.建立模型:物品n种,每种数量分别为k1,k2,..kn个,每种物品又有一个属性值p1,p2,…pn,(如本题的字母价值),
求属性值和为m的物品组合方法数。(若数量ki无穷 也成立,即对应下面式子中第ki项的指数一直到无穷)
2.构造母函数:G(x)=(1++…)(1+++…)…(1+++…) (一)
=a0 + a1*x + a2* + a3* +....+ akk* (设kk=k1·p1+k2·p2+…kn·pn) (二)
G(x)含义: ak 为属性值和为k的组合方法数。
母函数利用的思想:
1.把组合问题的加法法则和幂级数的乘幂对应起来。
2.把离散数列和幂级数对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来
确定离散数列的构造。
代码实现:
求G(x)时一项一项累乘。先令G=1=(1+0*x+0*+…0*),再令G=G*(1++…)得到形式(二)的式子…最后令G=G*(1+++…)。
模板:
const int MAX_N = 10000; int a[MAX_N]; // 保存函数的各项系数 int b[MAX_N]; // 中间量, 保存每一次的情况 int NumMin[MAX_N]; //每种最少数量 int NumMax[MAX_N]; //每种最多数量 int Val[MAX_N]; //每种的权重 int N; // 种数 int P; // 价值上限(即目标); memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b)); a[0] = 1; for (int i = 1; i <= N; i++) { // 循环每种函数(1 + a1 x ^ p1 + a2 x ^ p2 + ......) // 一般情况下NumMin 都是 0 // 如果每种情况都可以取无限个 就可以删去 j <= NumMAX[i] for (int j = NumMin[i]; j <= NumMax[i] && j * Val[i] <= P; j++) // 两个函数相乘 k 是被乘函数各项的指数 for (int k = 0; k + j * Val[i] <= P; k++) // k + j * Val[i] 是结果函数的指数 b[k + j * Val[i]] += a[k]; for (int j = 0; j <= P; j++) { a[j] = b[j]; b[j] = 0; } }
优化:
当数据规模特别大时,可以用一个 last 来标记目前最大的指数,这样只需要在 0 ~ last 上计算。
a[0] = 1; int last = 0; //因为有 last ,所以无需初始化其他位 for (int i = 0; i < N; i++) { int lastnext = min(last + NumMax[i]* Val[i], P); memset(b, 0, sizeof(lastnext + 1)); for (int j = NumMin[i]; j <= NumMax[i] && j * Val[i] <= lastnext; j++) for (int k = 0; k + j * Val[i] <= lastnext; k++) b[k + j * Val[i]] += a[k]; for (int j = 0; j <= P; j++) { a[j] = b[j]; b[j] = 0; } last = lastnext; }
// 这种方式应该不适用于无限个的情况
例题:
Problem Description:
然后包括N行数据,每行包括26个<=20的整数x1,x2,.....x26.
题解:
1.建模:物品(字母)26种,每种数量x1,x2…x26,属性值为1,2,3..26,求属性值和<=50的组合方法数。
AC Code#include<iostream> #include<cstdio> #include<cstring> using namespace std; long long a[60],b[60]; int main() { int n; cin>>n; while(n--) { memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); a[0]=1; int x; for(int i=1;i<=26;i++) { scanf("%d",&x); for(int j=0;j<=50;j++) { for(int k=0;k<=x&&(j+k*i<=50);k++) { b[j+k*i]+=a[j]; } } for(int j=0;j<=50;j++) { a[j]=b[j]; b[j]=0; } } long long ans=0; for(int i=1;i<=50;i++) ans+=a[i]; cout<<ans<<endl; } }
突然有一天假期结束,时来运转,人生才是真正开始了。