Ant Counting POJ - 3046 多重集组合数

题目链接:Ant Counting POJ - 3046

t种物品,每种物品有a[i]个,从中取出s个,s+1…b个,取法数相加有多少种?(同种物品没有区别)

就是挑战上的多重集组合数

n种物品,每种物品a[i]个,从中取出m个,取法有多少?
定义dp[i+1][j] := 从前i种物品种取j个,有多少种取法;
要在i种物品中取出j个,可以在i-1中物品种取j个,第i种物品不取
或者从前i-1中物品中取j-k个,k<=min(a[i], j),第i种物品取k个
即:dp[i+1][j] = dp[i][j-k] (0<=k<=min(a[i], j)
但这样需要三层循环,效率O(n*m*a[i]);

挑战上通过将上式变形的出O(mn)的公式,看不懂变形过程,但如果从实际出发,这个公式很好理解
dp[i+1][j] = dp[i][j] + dp[i+1][j-1] - dp[i][j-1-a[i]];
右边有三个部分
第一个:在前i-1种物品中取j个,第i种物品一个都不取

第二三个整体:前i种物品取了j-1个,其中第i种取了0-a[i]-1个,然后再在第i种物品种取一个

第二个部分:前i种物品取了j-1个,其中第i种取了0-a[i]个(也就是包括了第i种物品全部取了的情况,所以要减去)
第三部分:前i个物品取了j-1个,其中第i种取了a[i]个(看起来是前i-1种物品取了j-1-a[i]个,但只要再取a[i]个i物品,就是了)

还有就是,但’j-1-a[i]<0`时,第i种不可能全部取完,就不需要减去全部取完的情况了

i = 0..n-1;
j = 1..m;//(dp[i][0] = 1,因为无论i=多少,取0个的方案都只有一个,所以提前初始化dp[i][0] = 1;
if(j-1-a[i]>=0) dp[i+1][j] = dp[i][j] + dp[i+1][j-1] - dp[i][j-1-a[i]];
else dp[i+1][j] = dp[i][j] + dp[i+1][j-1];

而这题核心代码一样的,只需要将最后的dp[t][s…b]加起来就好了;
注意这个dp不能写成一位数组,因为每次更新都同时需要前面更新过的和没更新过的数据,但如果用二维数组,就需要dp[1000][100000],1e8的int可能会mle,所以用滚动数组就好啦

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

const int MAXT = 1000 + 10, MOD = 1000000;
int a[MAXT], t, n, s, b, dp[2][MAXT*100];

int main()
{
    while(cin >> t >> n >> s >> b)
    {
        memset(a, 0, sizeof(a));
        memset(dp, 0, sizeof(dp));
        int x;
        for(int i=0; i<n; ++i)
        {
            scanf("%d", &x);
            a[x-1]++;
        }

        dp[0][0] = dp[1][0] = 1;
        for(int i=0; i<t; ++i)
        {
            for(int j=1; j<=b; ++j)
            {
                if(j-1-a[i] >= 0) dp[(i+1)&1][j] = (dp[i&1][j] + dp[(i+1)&1][j-1] - dp[i&1][j-1-a[i]] + MOD) % MOD;
                else dp[(i+1)&1][j] = (dp[i&1][j] + dp[(i+1)&1][j-1]) % MOD;
            }
        }
        int ans = 0;
        for(int i=s; i<=b; ++i)
        {
            ans = (ans + dp[t&1][i]) % MOD;
        }
        cout << ans << endl;
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值