HDU&POJ训练记录4 生成函数

本文通过六道题目详细解析了如何使用多项式生成函数解决计数问题,包括拆分数、组合问题等,提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Ignatius and the Princess III

传送门

题意简述

求将n拆分成若干整数的方案数。

数据范围

n120

题解

首先构造用每一个数能构成的数的生成函数,然后相乘
(1+x1+x2+x3+...)(1+x2+x4+x6+...)(1+x3+x6+x9...)...
然后将多项式展开, xn 项的系数就是答案
那么可以 O(n3) 直接暴力求解多项式

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 150

int n;
int a[N],b[N];

int main()
{
    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<=n;k+=i)
                    b[j+k]+=a[j];
            for (int j=0;j<=n;++j) a[j]=b[j],b[j]=0;
        }
        printf("%d\n",a[n]);
    }
}

2、Square Coins

传送门

题意简述

求将n划分成若干个完全平方数的方案数。

数据范围

n300

题解

和上一题相似,构造用每一个完全平方数的生成函数,然后相乘
(1+x1+x2+x3+...)(1+x2+x4+x6+...)(1+x3+x6+x9...)...
然后将多项式展开, xn 项的系数就是答案
那么可以 O(n3) 直接暴力求解多项式

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 600

int n,m,qr[N],a[N],b[N];

int main()
{
    for (int i=1;i<=18;++i) qr[i]=i*i;
    while (~scanf("%d",&m))
    {
        if (!m) break;
        for (n=1;qr[n+1]<=m;++n);
        for (int i=0;i<=m;++i) a[i]=1,b[i]=0;
        for (int i=2;i<=n;++i)
        {
            for (int j=0;j<=m;++j)
                for (int k=0;k<=m;k+=qr[i])
                    b[j+k]+=a[j];
            for (int j=0;j<=m;++j) a[j]=b[j],b[j]=0;
        }
        printf("%d\n",a[m]);
    }
}

3、Holding Bin-Laden Captive!

传送门

题意简述

有a个1,b个2,c个5,问最小的无法组合出的数。

数据范围

a,b,c1000

题解

和之前的方法都是差不多的
分别构造1,2,5的生成函数,这里是一个有限数列
(1+x1+x2+...+xa)(1+x2+x4+...+x2b)(1+x5+x10+...+x5c)
将多项式展开,然后找一个次数最小的没有系数的项即可
时间 O(n2)

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 10000

int x,y,z,ans;
int a[N],b[N];

int main()
{
    while (~scanf("%d%d%d",&x,&y,&z))
    {
        if (!x&&!y&&!z) break;
        memset(a,0,sizeof(a));memset(b,0,sizeof(b));
        for (int i=0;i<=x;++i) a[i]=1;
        for (int i=0;i<=x;++i)
            for (int j=0;j<=y<<1;j+=2)
                b[i+j]+=a[i];
        for (int i=0;i<=x+(y<<1);++i) a[i]=b[i],b[i]=0;
        for (int i=0;i<=x+(y<<1);++i)
            for (int j=0;j<=z*5;j+=5)
                b[i+j]+=a[i];
        for (ans=1;ans<=x+(y<<1)+z*5;++ans)
            if (!b[ans]) break;
        printf("%d\n",ans);
    }
}

4、Big Event in HDU

传送门

题意简述

有n种东西,每一个的权值为v,有m个。将这些东西分成两拨,让权值和尽量接近。

数据范围

0<n,v50,0<m100

题解

判断读入结束是一个负数,并不一定是-1,wa了好久。。
首先用生成函数求出来那些权值能被组合出来,然后枚举一下[m/2..0]范围内的就行了
时间 O(nmv)

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 300000

int n,m,x,y;
int a[N],b[N];

int main()
{
    while (~scanf("%d",&n))
    {
        if (n<0) break;
        memset(a,0,sizeof(a));memset(b,0,sizeof(b));
        scanf("%d%d",&x,&y);
        for (int i=0;i<=y;++i) a[x*i]=1;
        m=x*y;
        for (int i=2;i<=n;++i)
        {
            scanf("%d%d",&x,&y);
            for (int j=0;j<=m;++j)
                for (int k=0;k<=y;++k)
                    b[j+x*k]+=a[j];
            m+=x*y;
            for (int j=0;j<=m;++j) a[j]=b[j],b[j]=0;
        }
        for (int i=m/2;i>=0;--i)
            if (a[i])
            {
                printf("%d %d\n",m-i,i);
                break;
            }
    }
}

5、Blocks

传送门

题意简述

用红黄蓝绿给n个格子染色,要求红色和绿色必须是偶数个,求方案数。对10007取模。

数据范围

T100,1n109

题解

因为牵扯到排列问题,需要用到指数型生成函数
设方案数为 an ,可以写出生成函数:
(1+x22!+x44!+...)2(1+x+x22!+x33!+...)2=(ex+ex2)2e2x=e2x+e2x+24e2x=e4x+2e2x+14=(4n1+2n1)n>0xnn!
所以这道题的答案就是 4n1+2n1

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define Mod 10007

int T,n;

int fast_pow(int a,int p)
{
    int ans=1;
    for (;p;p>>=1,a=a*a%Mod)
        if (p&1)
            ans=ans*a%Mod;
    return ans;
}
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d",&n);
        printf("%d\n",(fast_pow(4,n-1)+fast_pow(2,n-1))%Mod);
    }
}

6、排列组合

传送门

题意简述

有n种物品,每种有 ai 个,在这些物品中选出m个,求排列数。

数据范围

1n,m10

题解

排列问题,用到指数型生成函数
指数表示出现的次数,写出生成函数应该是 i=1n(1+x+x22!+x33!+...+xaiai!)
然后将多项式展开求第m项的系数
过程中暴力做多项式乘法用double,注意c++强转int是向0取整

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;

int n,m,x;
double mul[50],a[50],b[50];

int main()
{
    mul[0]=1.0;
    for (int i=1;i<=10;++i) mul[i]=mul[i-1]*(double)i;
    while(~scanf("%d%d",&n,&m))
    {
        memset(a,0,sizeof(a));memset(b,0,sizeof(b));
        scanf("%d",&x);
        for (int i=0;i<=min(m,x);++i) a[i]=1.0/mul[i];
        for (int i=2;i<=n;++i)
        {
            scanf("%d",&x);
            for (int j=0;j<=m;++j)
                for (int k=0;k<=min(m,x);++k)
                    b[j+k]+=a[j]*(1/mul[k]);
            for (int j=0;j<=m;++j) a[j]=b[j],b[j]=0.0;
        }
        printf("%.0lf\n",a[m]*mul[m]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值