2018年10月27日提高组 T1 Gift

版权声明:转载无所谓的。。。 https://blog.csdn.net/xuxiayang/article/details/83445409

大意

给定 n n 个物品,每个物品只能选一次,求出在空间不超过 m m 且不能再选任何一个物品时的方案数对 1 0 7 + 7 10^7+7 取模


思路

发现这玩意儿很像01背包啊,于是就有了

f [ i ] [ j ] = f [ i 1 ] [ j ] + f [ i 1 ] [ j a [ i ] ] f[i][j]=f[i-1][j]+f[i-1][j-a[i]]

然后呢,因为有不能再选任何一件物品这个约束,所以我们先排序,保证前面的小,然后枚举不能再选的物品,接着转移,这样子的复杂度是 O ( n 2 m ) O(n^2m) 的,期望得分60

接着我们发现,上述算法因为每次都 d p dp 一次所以时间不够,于是我们就想到了能否提前求好所有的值呢?答案是可以的。

首先每次我 d p dp 时只是不能选的物品多了一个,而其他地方实际上市没有区别的,所以我们换一种表示方法

f [ i ] [ j ] f[i][j] 表示后面的 i i 个数,还剩 j j 点空间时的方案数,得到方程:

f [ i ] [ j ] = f [ i + 1 ] [ j ] + f [ i + 1 ] [ j a [ i ] ] f[i][j]=f[i+1][j]+f[i+1][j-a[i]]

这样我们每次转移实际上就是 i i 往前挪一位,就不需要再用 d p dp 区转移了,时间复杂度 O ( n m ) O(nm)


代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ymw 10000007
using namespace std;
int a[1001],n,m,minn=2147,sum;
long long ans,f[1005][1001],now;
inline void write(long long x){if(x>9)write(x/10);putchar(x%10+48);return;}
signed main()
{
	freopen("1.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;i++) scanf("%d",a+i),sum+=a[i];
	if(sum<=m) return putchar(49)&0;//加在一起都没有m,那么只能全部都选
	sort(a+1,a+1+n);
	f[n+1][0]=1;
	for(register int i=n;i>0;i--)
	for(register int j=0;j<=m;j++)
	{
		f[i][j]=f[i+1][j];
		if(j>=a[i])(f[i][j]+=f[i+1][j-a[i]])%=ymw;//动态转移
	}
	for(register int x=1;x<=n;x++)
	{
		for(register int i=0;i<a[x];i++) 
		if(m-i>=0) (ans+=f[x+1][m-i])%=ymw;//计算在后面x+1个数的方案数
		m-=a[x];//选走这个物品,容量变少了
	}
	write(ans%ymw);//输出
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/83445409