题目大意:

老师今天买了好多黑色和白色巧克力(可以认为数量无限)来奖励同学们,让同学们自己来拿巧克力,但有如下限制:

1、每个人最少拿一块巧克力;

2、每个人只能拿一种颜色的巧克力;

3、拿黑色巧克力的人不少于C 个;

4、第i 个人最多拿a[i]个黑巧克力,或最多拿b[i]个白巧克力。

请问N(编号1~N)个同学拿巧克力可能的方案数是多少?(取模10007即可)

100%的数据:1≤N≤100000,1≤C≤20,1≤a[i],b[i]≤10^9。


思路:

明显是一道完全背包的问题。(巧克力数量是无限的)

但是这道题的数据很坑,如果我们直接打完全背包,时间复杂度则是O(n^2),很明显会超时。

那么我们可以换一种思路:

先求出拿巧克力的总方法,再用完全背包求出小于c人取黑巧克力的方案数(时间复杂度O(nc)),最后用总方法减去小于c人取黑巧克力的方案数,就是我们要求的答案。

状态转移方程:(f[i][j]=f[i-1][j-1]*a[i]+f[i-1][j]*b[i])


代码:

#include <cstdio>
#include <iostream>
using namespace std;

int f[100001][21],a[100001],b[100001],sum,ans;
int n,m;

int main()
{
freopen("fas.in","r",stdin);
freopen("fas.out","w",stdout);
scanf("%d%d",&n,&m);
sum=1;
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
sum=sum*(a[i]+b[i])%10007; //计算总方案数
}
f[0][0]=1;
for (int i=1;i<=n;i++) f[i][0]=(f[i-1][0]*(b[i]%10007))%10007; //初始化
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) //完全背包
f[i][j]=((f[i-1][j-1]*(a[i]%10007))%10007+(f[i-1][j]*(b[i]%10007))%10007)%10007; //当i个人中j个人取了黑巧克力时的方案数
for (int i=0;i<m;i++)
{
sum=(sum-f[n][i])%10007; //计算总方案数
if(sum<0) sum+=10007;
}
printf("%d\n",sum);
return 0;
}