母函数的一些简单总结

母函数的简单总结

 牛人的博客地址
      :http://blog.csdn.net/hhq420684/article/details/12876993
http://blog.csdn.net/YDYKL/article/details/6655142
http://blog.csdn.net/leonharetd/article/details/8922262
http://blog.csdn.net/lac001001/article/details/45137681
http://blog.csdn.net/MetalSeed/article/details/8046656
http://blog.csdn.net/zhongshijunacm/article/details/17481181


    做题时碰到了母函数,便简单的看了一下,并没有系统的学过组合数学,只是看了几位大牛的解释,才勉强明白了一点母函数。摘抄了一些定义:
1,把组合问题的加法法则和幂级数的乘幂对应起来
2,把离散数列和幂级数一一对应起来,把离散数列见的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来确定离散数列的构造


一:在谈论母函数问题之前,我们先看一个简单的问题描述:假如有两组数据(A,B)和(C,D),每组中选出一个构成一个组合,总共有几种选法?很显然总共有4种选法:AC,AD,BC,BD。而且很容易联想到这个式子(A+B)*(C+D)=A*C+A*D+B*C+B*D。式子中的几个乘积项就是上面的4种选法。假如把问题换一下:每组中选出一个或0个数据构成组合,总共有几种组合?那么结果就变成:{空},A,B,C,D,AC,AD,BC,BD,而式子(1+A+B)*(1+C+D)=1+C+D+A+A*C+A*D+B+B*C+B*D,正好和上面组合的结果又一致(1代表什么都没选)。从这2个例子我们可以发现多项式乘积和组合存在着某种关系。事实上我们可以这么理解:(1+A+B)可以理解为从第一组数据中取0个数据,取A或者取B,同样(1+C+D)可以理解为从第二组数据取0个数据,取C或者取D。两者相乘的结果就表示了所有的组合。再看一下这个多项式:

 (1+x)*(1+x+x2)*(1+x3)=1+2x+2x2+2x3+2x4+2x5+x6

这个多项式和上面的有一些区别了,它的幂级数超过1了。如果要从(1+x)、(1+x+x2)和(1+x3)中得到x的2次方的话,有两种选择:从(1+x)和(1+x+x2)中分别选择一个x或者从(1+x+x2)中选择x2;如果要得到x的6次方的话,只有1种选择,就是从(1+x)中选择x、(1+x+x2)中选择x2、(1+x3)中选择x3。也就是说乘积结果的每一项anxn的前面的系数an表示了从(1+x)、(1+x+x2)和(1+x3)中得到xn的组合数。

其实上面的例子就利用了母函数的思想,下面来具体讨论一下母函数

二:母函数就是n个多项式相乘,最终化为G(x)=a0+a1x+a2x^2+a3x^3+a4x^4+……+anx^n的式子,相应的系数对应相应的组成方式的个数。这个就是普通型母函数


三利用普通型母函数来解决一些组合问题

1.  有1克、2克、3克、4克砝码各一枚,问你能称出哪几种重量?每种重量各有几种方案?

下面是用母函数解决这个问题的思路:

首先,我们用X表示砝码,X的指数表示砝码的重量。那么,如果用函数表示每个砝码可以称的重量,

1个1克的砝码可以用函数X^0 + X^1表示,x^0代表不放的状态 x^1代表放一个1克的砝码。

1个2克的砝码可以用函数X^0 + X^2表示,x^0代表不放的状态 x^2代表放一个2克的砝码。

依次类推。

所以母函数 为 (1 + x) * (1 + x^2 ) * (1 + x^3 ) * (1 + x^4 )

解得方程 X^0 + X^1 + X^2 + 2*X^3 + 2*X^4 + 2*X^5 + 2*X^6 + 2*X^7 + X^8 + X^9 + X^10。

因为同底数幂相乘指数相加,所以 x^3*x^2 = x^5 说明一个3克砝码和一个2克砝码称出了5克,x^1*x^4 = x^5一个1克砝码和一个4克砝码称出了5克.

说明 该多项式的系数代表他解的个数 2*x^6代表称出6克有两种方案

2.拆分整数

以展开后的x为例,其系数为4,即4拆分成123之和的拆分数为4

即 :4=1+1+1+1=1+1+2=1+3=2+2

这里再引出两个概念整数拆分和拆分数:

所谓整数拆分即把整数分解成若干整数的和(相当于把n个无区别的球放到n个无标志的盒子,盒子允许空,也允许放多于一个球)。

整数拆分成若干整数的和,办法不一,不同拆分法的总数叫做拆分数

可以构造母函数G( x ) = ( 1 + x + x^2 +····) * ( 1 + x^2 + x^4 + ····) * ( 1 + x^3 + x^6),最终x^4d的系数为4的基本拆分总数。


四,下面我们给出网上一个基本的求母函数的模板,求母函数就是进行多项式的展开,程序实现多项式的展开和用手是实现多项式的展开是一样的,先取第一项的每项和第二项的每项相乘,得到的结果一次和下一项相乘,最终全部展开完毕

下面是模板代码:

<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
	int c1[1000];
	int c2[1000];
	int n;
	while (cin >> n)
	{
		for (int i = 0; i <= n; i++)
		{
			c1[i] = 1;//c1为已经计算得到的结果
			c2[i] = 0;//c2为正在计算的状态结果
		}
		for (int i = 2; i <= n; i++)//计算第一个多项式依次和后面的多项式相乘
		{
			for (int j = 0; j <= n; j++)//从前面一个多项式中X的0——n次方系数一次计算
			{
				for (int k = 0; k + j <= n; k += i)//后面一个多项式次方数一次为0,k+i,k+2i,x^0,x^2,x^4;x^n。因为只计算到x^n次方,所以k+j要小于n
				{
					c2[j + k] +=1* c1[j];//元素下标即为x的次方数.1代表第二项x^k的系数为1.x的k次方与x的j次方相乘的值放在x^(j+k)中
				}//共有n个多项式,只计算保留x^n前的系数,因为需要x^n的系数
			}
			for (int i = 0; i <= n; i++)//赋值给c2
			{
				c1[i] = c2[i];
				c2[i] = 0;
			}
		}
		cout << c1[n] << endl;
	}
	return 0;
}</span>
这就是基本的模板代码

下面介绍杭电上的一些题目:

1.1.  题目:http://acm.hdu.edu.cn/showproblem.php?pid=1028

就是直接运用模板
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
    int c1[1000];
    int c2[1000];
    int n;
    while (cin >> n)
    {
        for (int i = 0; i <= n; i++)
        {
            c1[i] = 1;
            c2[i] = 0;
        }
        for (int i = 2; i <= n; i++)//计算第一个多项式依次和后面的多项式相乘
        {
            for (int j = 0; j <= n; j++)//从前面一个多项式中X的0——n次方系数一次计算
            {
                for (int k = 0; k + j <= n; k += i)//后面一个多项式次方数一次为0,k+i,k+2i,x^0,x^2,x^4;x^n
                {
                    c2[j + k] +=1* c1[j];//元素下标即为x的次方数.1代表第二项x^k的系数为1.x的k次方与x的j次方相乘的值放在x^(j+k)中
                }//共有n个多项式,只计算保留x^n前的系数,因为需要x^n的系数
            }
            for (int i = 0; i <= n; i++)
            {
                c1[i] = c2[i];
                c2[i] = 0;
            }
        }
        cout << c1[n] << endl;
    }
    return 0;
}

</span>

2.  题目:http://acm.hdu.edu.cn/showproblem.php?pid=1398
这题基本是套模板,就是在循环条件略微改了一下
ac代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
//    int key[400];
    int n;
    while(cin>>n&&n)
    {
        int c1[400];
        int c2[400];
        for(int m=0;m<=n;m++)
        {
            c1[m]=1;
            c2[m]=0;
        }
       for(int i=2;i*i<=n;i++)
       {
           for(int j=0;j<=n;j++)
           {
               for(int k=0;j+k<=n;k+=i*i)//注意循环条件
               {
                   c2[j+k]+=1*c1[j];
               }
           }
           for(int v=0;v<=n;v++)
           {
               c1[v]=c2[v];
               c2[v]=0;
           }
       }
       cout<<c1[n]<<endl;
    }
    return 0;
}

</span>

3 http://acm.hdu.edu.cn/showproblem.php?pid=1085
建议这题数组开大点,这题先把所以组合情况记录下来,找到系数为0的指数就是无法组成的,若都可以的话,那sum+1肯定不可以,是最小了的
AC代码
<span style="font-size:18px;">#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int main()
{
	int c1[110000];
	int c2[110000];
	int a[3] = { 1, 2, 5 };
	int b[3];
	while (cin >> b[0]>> b[1]>> b[2])
	{
		if (b[0] == 0 && b[1] == 0 && b[2] == 0)
		{
			break;
		}
		memset(c1, 0, sizeof(c1));
		memset(c2, 0, sizeof(c2));
		int sum = b[0] + b[1]* 2 + b[2] * 5;
		for (int i = 0; i <= b[0]; i++)
		{
			c1[i] = 1;
		}
		for (int i = 1; i < 3; i++)
		{
			for (int j = 0; j <= sum; j++)
			{
				for (int k = 0, v = 0; v <= b[i]; k += a[i],v++)
				{
					c2[k + j] += c1[j];
				}
			}
			for (int j = 0; j <= sum; j++)
			{
				c1[j] = c2[j];
				c2[j] = 0;
			}
		}
		int i;
		for ( i = 0; i <= sum; i++)
		{
			if (c1[i] == 0)
			{
				cout << i << endl; 
				break;
			}
		}
		if (i == sum+1)
		{
			cout << sum + 1 << endl; 
		}

	}
	return 0;
}</span>

4.  http://acm.hdu.edu.cn/showproblem.php?pid=1709
给你天平和砝码,让你判断1到s之间的重量那个是不可以求得,s为所有砝码的质量之和,这题要注意砝码既可以放一边,也可以两边都放,那么就是既有加的情况,也有减的情况。
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int abs(int n)
{
    return n<0?(-n):n;
}
int main()
{
    int c1[15000];
    int c2[15000];
    int a[101];
    int result[10000];
    int n;
    while(cin>>n)
    {
        int i,j,k;
        int sum=0;
      memset(c1,0,sizeof(c1));
      memset(c2,0,sizeof(c2));
      for( i=0;i<n;i++)
      {
          cin>>a[i];
          sum+=a[i];
      }
      for( i=0;i<=a[0];i+=a[0])
      {
          c1[i]=1;
      }
      for(i=1;i<n;i++)
      {
          for(j=0;j<=sum;j++)
          {
              for(k=0;k<=a[i];k+=a[i])
              {
                  c2[k+j]+=c1[j];
                  c2[abs(k-j)]+=c1[j];
              }
          }
          for(j=0;j<=sum;j++)
            {
              c1[j]=c2[j];
              c2[j]=0;
          }
      }
      int count=0;
      for(i=0;i<=sum;i++)
      {
          if(c1[i]==0)
          {
         result[count++]=i;
          }
      }
      cout<<count<<endl;
      if(count!=0)
      {
          for(i=0;i<count-1;i++)
          {
              cout<<result[i]<<' ';
          }
          cout<<result[count-1]<<endl;
      }
    }
    return 0;
}

</span>

5.  http://acm.hdu.edu.cn/showproblem.php?pid=1171
这题同样用母函数来解,只不过注意指数的变化范围,还有要求的是sum/2左右系数不为0的指数
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
struct f
{
    int v;
    int num;
}st[70];
int c1[260000];
int c2[260000];
int main()
{
    int n,len;
    while (cin >> n&&n > 0)
    {
        len = 0;
        for (int i = 0; i < n; i++)
        {
            cin >> st[i].v >> st[i].num;
            len += st[i].v*st[i].num;
        }
        
        memset(c1, 0, len*sizeof(c1[0]));
        memset(c2, 0, len*sizeof(c2[0]));
        for (int i = 0; i <= st[0].num*st[0].v; i+=st[0].v)
        {
            c1[i] = 1;
        }
        for (int i = 1; i <= n-1; i++)
        {
            for (int j = 0; j <= len; j++)
            {
                for (int k = 0;(k<=st[i].num*st[i].v)&& (k+j <= len); k += st[i].v)
                {
                    c2[j + k] += 1 * c1[j];
                }
            }
            for (int i = 0; i <= len; i++)
            {
                c1[i] = c2[i];
                c2[i] = 0;
            }
        }
        int v;
        for ( v = (len >> 1); v >= 0; v--)
        {
            if (c1[v]!=0)
            {
                break;
            }
        }
        cout << len - v<< ' ' << v << endl;
    }
    return 0;
}</span>

6.  http://acm.hdu.edu.cn/showproblem.php?pid=2069
给你1,5,10,25,50不同的硬币,在给你一个钱数,问这些硬币组成这个钱数有多少种不同的方法,但是要求所需的总的硬币个数要小于等于100
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
    int n;
    int c1[500][102];//前面表示硬币能够表示的钱数,后面表示表示这个钱数需要多少硬币
    int c2[500][102];//前面表示硬币能够表示的钱数,后面表示表示这个钱数需要多少硬币
    int a[5]={1,5,10,25,50};
    while(cin>>n)
    {
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));
        int i,j,k;
     for(i=0;i<=100;i++)
     {
         c1[i][i]=1;
     }     
     for( i=1;i<5;i++)
     {
         for(j=0;j<=n;j++)
         {
             for(k=0;k+j<=n;k+=a[i])
             {
                for(int v=0;v+k/a[i]<=100;v++)//判断所需硬币数量有没有超过100
                 c2[k+j][v+k/a[i]]+=c1[j][v];//表示同一钱币所以不同的硬币数量相加
             }    
         }
         for(k=0;k<=n;k++)
         {
         for(j=0;j<102;j++)
         {
             c1[k][j]=c2[k][j];
             c2[k][j]=0;
         }
         }
     }
     int sum=0;
     for(i=0;i<=100;i++)
     {
         sum+=c1[n][i];//得到钱数小于100总的表示方法
     }
     cout<<sum<<endl;
    }
    return 0;
}</span>

7 . http://acm.hdu.edu.cn/showproblem.php?pid=2152
AC代码 同样简单母函数
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
    int n,m;
    int a[101][2];
    int c1[200];
    int c2[200];
    while(cin>>n>>m)
    {
        int i,j,k;    
        for(i=0;i<n;i++)
        {
            cin>>a[i][0]>>a[i][1];
        }
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));
            for(i=a[0][0];i<=a[0][1];i++)
            {
                c1[i]=1;
            }
            for(i=1;i<n;i++)
            {
                for(j=0;j<=m;j++)
                {
                    for(k=a[i][0];k<=a[i][1];k++)
                    {
                        c2[j+k]+=c1[j];
                    }
                }
                for(j=0;j<=m;j++)
                {
                    c1[j]=c2[j];
                    c2[j]=0;
                }
            }
            cout<<c1[m]<<endl;
    }
    return 0;
}</span>

8.http://acm.hdu.edu.cn/showproblem.php?pid=2566
和上面一题2069一样
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int c1[1000][1000];
int c2[1000][1000];
int main()
{
    int n,m,t;

    int a[3]={1,2,5};
    int i,j,k,v;
    cin>>t;
    while(t--)
    {
        cin>>n>>m;
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));
        for( i=0;i<=n;i++)
        {
            c1[i][i]=1;
        }
        for(i=1;i<3;i++)
        {
            for(j=0;j<=m;j++)
            {
                for(k=0;k+j<=m;k+=a[i])
                {
                    for(v=0;v+k/a[i]<=n;v++)
                    {
                        c2[k+j][v+k/a[i]]+=c1[j][v];
                    }
                }
            }
            for(j=0;j<=m;j++)
            {
                for(k=0;k<=n;k++)
                {
                    c1[j][k]=c2[j][k];
                    c2[j][k]=0;
                }
            }
        }
        cout<<c1[m][n]<<endl;
    }
    return 0;
}</span>


9.http://acm.hdu.edu.cn/showproblem.php?pid=2189
注意组成数为素数,所以次方相加为素数的倍数
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int prime[100];
bool isprime(int n)
{
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0)
            return false;
    }
    return true;
}

int main()
{
    int key=0;
    int i,j,k,t;
for(i=2;i<=150;i++)
{
    if(isprime(i))
    {
        prime[key++]=i;
    }
}
int n;
cin>>t;
int c1[200];
int c2[200];
while(t--)
{
    cin>>n;
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
for(i=0;i<=n;i+=prime[0])
{
    c1[i]=1;
}
for(i=1;i<key;i++)
{
    for(j=0;j<=n;j++)
    {
        for(k=0;k+j<=n;k+=prime[i])
        {
            c2[j+k]+=1*c1[j]+0*c2[k];
        }
    }
    for(j=0;j<=n;j++)
    {
        c1[j]=c2[j];
        c2[j]=0;
    }
}
cout<<c1[n]<<endl;
}
return 0;
}</span>


太晚了,先写这么多吧




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值