【JZOJ5184】Gift【DP】【01背包】
题目大意:
题目连接:https://jzoj.net/senior/#main/show/5184
题目图片:
http://wx4.sinaimg.cn/mw690/0060lm7Tly1fwmsvyi1y8j30ow0na0um.jpg
http://wx1.sinaimg.cn/mw690/0060lm7Tly1fwmswti8zjj30p20fnt9r.jpg
给出一串数字,要求选择的数字和不能超过,而且还能选就得选。求方案数。
思路:
01背包变形。
设表示选择了第到第个数字,和为的方案数。那么很明显就有
那么,对于求答案,我们可以枚举现在没有选择的最小的数字
是,那么这也就说明前面的数字(经过排序后就比它更小)都被选择了。那么再枚举一个表示还能选择比小的数(按照原题来说就是还剩块钱),那么必须比大,不然的话就可以再选择了。
那么就设表示前个数的和。
那么就有
时间复杂度:
代码:
#include <cstdio>
#include <algorithm>
#define N 1100
#define MOD 10000007
using namespace std;
int n,m,p[N],f[N][N],s[N],ans;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&p[i]);
sort(p+1,p+1+n);
for (int i=1;i<=n;i++)
s[i]=s[i-1]+p[i]; //求前缀和
if (m>=s[n]) return !printf("1"); //特判,全部都可以选就只有一种情况
f[n+1][0]=1;
for (int i=n;i>=1;i--)
for (int j=0;j<=m;j++)
if (j>=p[i]) f[i][j]=(f[i+1][j]+f[i+1][j-p[i]])%MOD;
else f[i][j]=f[i+1][j];
for (int i=1;i<=n;i++)
for(int j=0;j<p[i];j++)
if (m-s[i-1]-j>=0)
ans=(ans+f[i+1][m-s[i-1]-j])%MOD;
printf("%d\n",ans);
return 0;
}