acm 组合数学及其应用--母函数

追逐青春的梦想,怀着自信的心,永不放弃

1、假设有x1个字母A,x2个字母B,……,x26个字母Z,同时假设字母A的价值为1,字母B的价值为2,……,字母Z的价值为26。那么,对于给定的字母,可以找到多少价值不大于50的单词呢?单词的价值就是组成一个单词的所有字幕的价值之和。比如,单词ACM的价值是1+3+14=18(组成的单词与排列顺序无关,比如ACM和MCA认为是同一单词)。

输入:输入首先是一个整数N,代表测试实例的个数。然后包括N行数据,每行包括26个不大于20的整数x1,x2,……,x26
输出:对于每个测试实例,请输出能找到的总价值不大于50的单词数,每个势力的输出占一行。

输入样例:

2
1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9 2 6 2 10 2 2 5 6 1 0 2 7 0 2 2 7 5 10 6 10 2 10 6 1 9

样例输出:

7
379297

分析:转化为求x的指数小于等于50的系数和

代码:


#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<map>
#include<stack>
#include<queue>
#include<set>
#include<sstream>
#include<ctime>
#include<algorithm>
#include<bits/stdc++.h>
#include<sstream>
#include<list>
#include<cmath>
#include<deque>
#include<cstdlib>
using namespace std;
const int maxn = 10086;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define pi acos(-1.0)
typedef long long LL;
void anhangduru(){
     string line;
     while(getline(cin,line)){
        int sum=0;
        int x;
        stringstream ss(line);
        while(ss>>x){sum+=x;}//按空格读入
     }
}//按行读入
//加上符号重载
int main()
{
//  ios::sync_with_stdio(0);//输入输出挂
    int n;
    scanf("%d",&n);
    int a[60],b[60];
    while(n--){
        int num;
        for(int i=0;i<=60;i++){
            a[i]=0;
            b[i]=0;
        }
        a[0]=1;
        for(int i=1;i<=26;i++){
            scanf("%d",&num);
            if(num==0)continue;
            for(int j=0;j<=50;j++)
                for(int k=0;k<=num&&k*i+j<=50;k++)
                    b[k*i+j]+=a[j];


            for(int j=0;j<=50;j++){
                a[j]=b[j];
                b[j]=0;
            }
        }
        int tot = 0;
        for(int i=1;i<=50;i++){
            tot += a[i];
        }
        printf("%d\n",tot);
    } 
    return 0;
}

这段代码的核心是:

a[0]=1;
        for(int i=1;i<=26;i++){
            scanf("%d",&num);
            if(num==0)continue;
            for(int j=0;j<=50;j++)
                for(int k=0;k<=num&&k*i+j<=50;k++)
                    b[k*i+j]+=a[j];
            for(int j=0;j<=50;j++){
                a[j]=b[j];
                b[j]=0;
            }
        }
2、给一个正整数N,我们定义这样一个等式:
N=a[1]+a[2]+a[3]++a[m] N = a [ 1 ] + a [ 2 ] + a [ 3 ] + … … + a [ m ]
,其中
a[i]>0,1<=m<=N a [ i ] > 0 , 1 <= m <= N
。现在,给一个整数N,可以找到多少个不同的等式。例如,当N等于4时,我们可以找到:
4=4 4 = 4
4=3+1 4 = 3 + 1
4=2+2 4 = 2 + 2
4=2+1+1 4 = 2 + 1 + 1
4=1+1+1+1 4 = 1 + 1 + 1 + 1
因此,当N等于4时结果是5。

输入:输入包含几组测试样例。每一组测试样例包含一个正整数N(1<=N<=120)。
输出:对于每个测试实例,请输出能找到的不同等式的个数,每个实例的输出占一行。
样例输入:

4
10
20

样例输出:

5
42
627

分析:求正整数N的拆分数。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<map>
#include<stack>
#include<queue>
#include<set>
#include<sstream>
#include<ctime>
#include<algorithm>
#include<bits/stdc++.h>
#include<sstream>
#include<list>
#include<cmath>
#include<deque>
#include<cstdlib>
using namespace std;
const int maxn = 10086;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define pi acos(-1.0)
typedef long long LL;
void anhangduru(){
     string line;
     while(getline(cin,line)){
        int sum=0;
        int x;
        stringstream ss(line);
        while(ss>>x){sum+=x;}//按空格读入
     }
}//按行读入
//加上符号重载
int main()
{
//  ios::sync_with_stdio(0);//输入输出挂
    int n;
    int a[121],b[121];
    while(~scanf("%d",&n)){
        for(int i=0;i<=n;i++){
            a[i]=1;
            b[i]=0;
        }
        for(int i=2;i<=n;i++){
            for(int j=0;j<=n;j++){
                for(int k=0;k+j<=n;k+=i){
                    b[k+j]+=a[j];
                } 
            }
            for(int j=0;j<=n;j++){
                a[j]=b[j];
                b[j]=0;
            }
        }
        printf("%d\n",a[n]);
    }
    return 0;
}

这段代码的关键点:

for(int i=2;i<=n;i++){
            for(int j=0;j<=n;j++){
                for(int k=0;k+j<=n;k+=i){
                    b[k+j]+=a[j];
                } 
            }
            for(int j=0;j<=n;j++){
                a[j]=b[j];
                b[j]=0;
            }
        }
3、Ferrers图像

定理1:

整数n拆分成k个数的和的拆分数,与数n拆分成最大数为k的拆分数相等

定理2:

整数n拆分成最多不超过m个数的和的拆分数,与n拆分成最大不超过m的拆分数相等

定理3:

整数n拆分成互不相同的若干奇数的和的拆分数,与n拆分成自共轭的Ferrers图像的拆分数相等

定理4:

正整数n拆分成不超过k个数的和的拆分数,等于将n+k拆分成敲好k个数的拆分数

4、指数型母函数

有n种物品,并且知道每种物品的数量。要求从中选出m件物品的排列数。例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有”AB”,”BA”两种。

输入:每组输入数据有两行,第一行是二个数n,m(1<=m,n<=10),表示物品数,第二行有n个数,分别表示这n件物品的数量。

输出:对应每组数据输出排列数。(任何运算不会超出2^31的范围)。
样例输入:

2 2 1 1

样例输出:

2

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long LL;
using namespace std;
const int N = 11;
double c1[N],c2[N];     //注意类型
LL fac[N];

void cal(){
    fac[0]=1;           //0!会被用到
    for(int i=1;i<N;i++)
        fac[i]=i*fac[i-1];
}

int main(){
    cal();
    int n,r;
    while(~scanf("%d%d",&n,&r)){
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));

        c1[0]=1;
        int num;
        for(int i=1;i<=n;i++){
            scanf("%d",&num);
            if(num==0) continue;
            for(int j=0;j<=r;j++){
                for(int k=0;k<=num&&k+j<=r;k++){
                    c2[k+j]+=c1[j]/fac[k];
                }
            }
            for(int j=0;j<=r;j++){
                c1[j]=c2[j];
                c2[j]=0;
            }
        }

        printf("%.0lf\n",c1[r]*fac[r]);
    }

    return 0;
}

关键代码:

c1[0]=1;
        int num;
        for(int i=1;i<=n;i++){
            scanf("%d",&num);
            if(num==0) continue;
            for(int j=0;j<=r;j++){
                for(int k=0;k<=num&&k+j<=r;k++){
                    c2[k+j]+=c1[j]/fac[k];
                }
            }
            for(int j=0;j<=r;j++){
                c1[j]=c2[j];
                c2[j]=0;
            }
        }

与第一个题目是类似的。注意题目描述的差别

5、求n位十进制数中出现偶数个5的数的个数。

这道题可以使用数位dp来解决,但是也可以使用母函数的办法。
令xn表示n位十进制数中出现偶数个5的数的个数,令yn表示n为十进制数中出现奇数个5的数的个数,则有如下分析:
当n>1时:xn = 9xn-1 + yn-1且yn = 9yn-1 + xn-1,其中,x1 = 8,y1 = 1。当n>1时,x1指的是数的最高位,可以取5以外的1,2,3,4,6,7,8,9八个数中的一个,因为数的最高位没有0,所以x1不能取0。利用公式可以地推算出x2,y2,……,xn,yn
但是,当n=1时:
xn = 9,yn = 1

6、斐波那契数列

递推公式:

f[i]=f[i-1]+f[i-2],i>=2

如果i特别大,可以考虑使用矩阵快速幂的方法进行求解。

7、斯特林数

第一类斯特林数:

有正负的,其绝对值是包含n个元素的集合分作k个环排列的方法数目。

递推公式为:

S(n,0)=0 S ( n , 0 ) = 0
S(1,1)=1 S ( 1 , 1 ) = 1
S(n+1,k)=S(n,k1)+nS(n,k) S ( n + 1 , k ) = S ( n , k − 1 ) + n S ( n , k )

第二类斯特林数:

是把包含n个元素的集合划分为正好k个非空子集的方法的数目。

递推公式为:

S(n,k)=0(n<kork=0) S ( n , k ) = 0 ( n < k o r k = 0 )
S(n,n)=S(n,1)=1 S ( n , n ) = S ( n , 1 ) = 1
S(n,k)=S(n1,k1)+kS(n1,k) S ( n , k ) = S ( n − 1 , k − 1 ) + k S ( n − 1 , k )

例题:将n个有区别的求放入k个无标号的盒子中(盒子不能为空)的方案数

8、卡特兰数

前几项为$$1、1、2、5、14、42、132……
递推关系:

h(n) = C(2n,n)/(n+1) (n=1,2,3,……)

例题1:一个栈(无穷大)的进栈序列为1,2,3,……,你,有多少个不同的出栈序列。

例题2:一堆火车以严格的顺序到一个站里,问出来的时候有多少种顺序
输入:输入包含几个例子,每个例子包含一个正整数N(N小于等于100)
输出:输出所有的火车从车站里出来有多少种顺序。
样例输入:

1
2
3
10

样例输出:

1
2
5
16796

分析:由于结果会很大,需要用大数来解决,公式为:h(n) = h(n-1)*(4*n-2)/n+1.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海燕技术栈

你的鼓励我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值