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;
}