母函数分:普通型母函数,指数型母函数。
————选自
Tanky Woo
普通型母函数主要是来求组合的方案数,而指数型母函数是求多重排列数。
关于普通型母函数的讲解,以前写过:
http://www.wutianqi.com/?p=596
而指数型母函数主要是关于排列组合方面的问题。
分别看两个比较典型的问题对比:
普通母函数问题:有红球两个,白球、黄球各一个,试求有多少种不同的组合方案。
指数型母函数问题:
假设有8个元素,其中a1重复3次,a2重复2次,a3重复3次。从中取r个组合,求其组合数。
下面是指数型母函数的定义:
对于上面的问题“假设有8个元素,其中a1重复3次,a2重复2次,a3重复3次。从中取r个组合,求其组合数。”:
(感谢 3Dnn 同学指出,下图的 28/3! 应该改为 26/3!)
学习了,复习了,预习了,又复习了下母函数。
今天刷了两道指数型母函数的水题。
1. hdu 1521 http://acm.hdu.edu.cn/showproblem.php?pid=1521
题意很清楚,但是我发现如果利用以前所学的一点排列组合的基础,貌似这个很难找到排列组合公式。 用dp绝对可以,但是可能会稍有点复杂。如果你看过指数型母函数就知道这个很基础了。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #include <math.h> #include <map> #include <queue> #include <sstream> #include <iostream> using namespace std; #define INF 0x3fffffff #define __int64 long long int typedef __int64 LL; int n,m; int save[13]; int sum[13]; int tmp[13]; int g[13]; int main() { //freopen("//home//chen//Desktop//ACM//in.text","r",stdin); //freopen("//home//chen//Desktop//ACM//out.text","w",stdout); save[0]=1; for(int i=1;i<=12;i++) { save[i]=save[i-1]*i; } while(scanf("%d%d",&n,&m)!=EOF) { for(int i=1;i<=n;i++) scanf("%d",g+i); memset(tmp,0,sizeof(tmp)); memset(sum,0,sizeof(sum)); for(int i=0;i<=g[1];i++) { sum[i]=1; } int cnt; for(int i=2;i<=n;i++) { for(int j=0;j<=m;j++) for(int k=0;k<=g[i];k++) { if(j+k<=m) { cnt=save[j+k]/(save[j]*save[k]); tmp[j+k] += cnt*sum[j]; } } for(int j=0;j<=m;j++) { sum[j]=tmp[j]; tmp[j]=0; } } printf("%d ",sum[m]); } return 0; }
2. hdu 1261 http://acm.hdu.edu.cn/showproblem.php?pid=1261
这题和上面那题思路几乎一样,但是这题数据用long long 是存不下的,所以可以用java或大数。(我开始爱上java了)
可以用基础的像上题一样的解法写,模拟多项式相乘。 但是这题有个特殊的地方,就是会把所有的出现了的元素全部都选上, 于是在用指数型母函数将式子展开后,就会发现 x^n/n! (n为所有元素的和)这个项的系数为n!/(n1!*n2!*...*nk!). 这就可以看出母函数在优化方面往往有重大贡献。 这也是母函数的最重要的一种用法吧
附一份接近超时的模拟多项式相乘的java代码:
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ import java.util.Scanner; import java.math.BigInteger; /** * * @author chen */ public class Main { /** * @param args the command line arguments */ public static void main(String[] args) { // TODO code application logic here int n; Scanner in=new Scanner(System.in); n=in.nextInt(); BigInteger[]save; save=new BigInteger[320]; save[0]=BigInteger.valueOf(1); // 原来java每次定义数组都要这样做;先声明然后在申请空间 for(int i=1;i<320;i++) { BigInteger tmp1=BigInteger.valueOf(i); save[i]=save[i-1].multiply(tmp1); } //for(int i=0;i<=26;i++) //System.out.println(sum[i]); while(n!=0) { BigInteger sum[]; sum = new BigInteger[320]; BigInteger tmp[]; tmp = new BigInteger[320]; int g[]; g = new int[30]; int num; num=0; for(int i=1;i<=n;i++) { g[i]=in.nextInt(); num+=g[i]; } for(int i=0;i<320;i++) { sum[i]=BigInteger.valueOf(0); tmp[i]=BigInteger.valueOf(0); } for(int i=0;i<=g[1];i++) sum[i]=BigInteger.valueOf(1); for(int i=2;i<=n;i++) { for(int j=0;j<=num;j++) for(int k=0;k<=g[i];k++) { BigInteger tmp1; if(j+k<=num) { tmp1=save[j+k].divide(save[j].multiply(save[k])); tmp[j+k]=tmp[j+k].add( sum[j].multiply(tmp1) ); } } for(int j=0;j<=num;j++) { sum[j]=tmp[j]; tmp[j]=BigInteger.valueOf(0); } } System.out.println(sum[num]); n=in.nextInt(); } } }