题目大意:
题目连接:https://jzoj.net/senior/#main/show/5184
题目图片:
http://wx4.sinaimg.cn/mw690/0060lm7Tly1fwmsvyi1y8j30ow0na0um.jpg
http://wx1.sinaimg.cn/mw690/0060lm7Tly1fwmswti8zjj30p20fnt9r.jpg
给出一串数字,要求选择的数字和不能超过
m
m
m,而且还能选就得选。求方案数。
思路:
01背包变形。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示选择了第
i
i
i到第
n
n
n个数字,和为
j
j
j的方案数。那么很明显就有
f
[
i
]
[
j
]
=
f
[
i
+
1
]
[
j
]
/
∗
不
选
这
个
数
∗
/
+
f
[
i
+
1
]
[
j
−
p
[
i
]
]
/
∗
选
这
个
数
∗
/
f[i][j]=f[i+1][j]/*不选这个数*/+f[i+1][j-p[i]]/*选这个数*/
f[i][j]=f[i+1][j]/∗不选这个数∗/+f[i+1][j−p[i]]/∗选这个数∗/
那么,对于求答案,我们可以枚举现在没有选择的最小的数字
是
i
i
i,那么这也就说明
i
i
i前面的数字(经过排序后就比它更小)都被选择了。那么再枚举一个
j
j
j表示还能选择比
j
j
j小的数(按照原题来说就是还剩
j
j
j块钱),那么
j
j
j必须比
p
[
i
]
p[i]
p[i]大,不然的话就可以再选择
p
[
i
]
p[i]
p[i]了。
那么就设
s
[
i
]
s[i]
s[i]表示前
i
i
i个数的和。
那么就有
a
n
s
=
∑
i
=
1
n
∑
j
=
0
p
[
i
]
f
[
i
+
1
]
[
m
−
s
[
i
]
−
j
]
ans=\sum^{n}_{i=1}\sum^{p[i]}_{j=0}f[i+1][m-s[i]-j]
ans=∑i=1n∑j=0p[i]f[i+1][m−s[i]−j]
时间复杂度:
O
(
n
m
)
O(nm)
O(nm)
代码:
#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;
}