母函数运用

应用:

n n n种物品,第 i i i件物品的价值为 v i vi vi,你拿它的数量下限为 n 1 i n1_i n1i,上限为 n 2 i n2_i n2i

那么就可以生产以下母函数:
∏ i = 1 n ( x v i ∗ n 1 i + x v i ∗ ( n 1 i + 1 ) + . . . + x v i ∗ ( n 2 i − 1 ) + x v i ∗ n 2 i ) \prod_{i=1}^n\big( x^{vi*n1_i}+x^{vi*(n1_i+1)}+...+x^{vi*(n2_i-1)}+x^{vi*n2_i} \big) i=1n(xvin1i+xvi(n1i+1)+...+xvi(n2i1)+xvin2i)

对其运算结果 b 0 ∗ x 0 + b 1 ∗ x 1 + . . . + b m a x ∗ x m a x b_0*x^0+b_1*x^1+...+b_{max}*x^{max} b0x0+b1x1+...+bmaxxmax , x i x^i xi的系数 b i b_i bi即为取总价值为 i i i的方案数


代码实现:

在数学的角度上看,到了每一个 i i i,只要拿出这个大项中的每一个小项 a ∗ x j a*x^j axj去乘之前得到的 b ∗ x k b*x^k bxk,再把 a ∗ b a*b ab加到 x j + k x^{j+k} xj+k里面就可以了

不过在算法的角度,好吧,也没设么优化,只能暴力了

  • 如果是直接开矩阵存的话最省时间,但是空间比较费
  • 省点空间就是滚动数组,但是在数组滚回来的时候需要memset滚回来的数组,不过好在我们知道最大项有多大,只要更新到最大的那个就好

代码:

#include<bits\stdc++.h>
using namespace std;
const int N=1000;

int a[N],b[N];//x^i的系数
int v[N],n1[N],n2[N];
int n,P;//物品数,总上限

int main(){
    scanf("%d%d",&n,&P);
    memset(a,0,sizeof(a));
    a[0]=1;
    int last=0;
    for(int i=1;i<=n;i++)scanf("%d",v+i);
    for(int i=1;i<=n;i++)scanf("%d",n1+i);
    for(int i=1;i<=n;i++)scanf("%d",n2+i);
    for(int i=1;i<=n;i++){
        int last2=min(last+n2[i]*v[i],P);//下一次的last

        memset(b,0,sizeof(int)*(last2+1));//只清空b[0..last2]

        for(int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++){//取j个i物品
            for(int k=0;k<=last&&k+j*v[i]<=last2;k++){//k为取j个i之前的状态
                b[k+j*v[i]]+=a[k];
            }
        }
        memcpy(a,b,sizeof(int)*(last2+1));//b赋值给a,只赋值0..last2
        last=last2;//更新last
    }
}

例题:找单词

原题:hdu 2083

题意:

26个字母各a[i]个,a的价值为1,z为26,求有多少种选择方式(无顺序)组成的单词价值少于等于50

解析:

模板的不能再模板了吧,巧的是,我之前不知道母函数的时候用的是多重背包,现在感觉代码都差不多

代码:

#include<bits\stdc++.h>
using namespace std;
const int N=10000;

int a[N],b[N];//x^i的系数
int v[30],n1[30],n2[30];
int n,P;//物品数,总上限

int main(){
    int t;scanf("%d",&t);
while(t--){
    n=26,P=50;
    memset(a,0,sizeof(a));
    a[0]=1;
    int last=0;
    for(int i=1;i<=26;i++)v[i]=i;
    for(int i=1;i<=n;i++)n1[i]=0;
    for(int i=1;i<=n;i++)scanf("%d",&n2[i]);

    for(int i=1;i<=n;i++){
        int last2=min(last+n2[i]*v[i],P);//下一次的last

        memset(b,0,sizeof(int)*(last2+1));//只清空b[0..last2]

        for(int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++){//取j个i物品
            for(int k=0;k<=last&&k+j*v[i]<=last2;k++){//k为取j个i之前的状态
                b[k+j*v[i]]+=a[k];
            }
        }
        memcpy(a,b,sizeof(int)*(last2+1));//b赋值给a,只赋值0..last2
        last=last2;//更新last
    }
    int ans=0;for(int i=1;i<=50;i++)ans+=a[i];
    printf("%d\n",ans);
}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值