世界真的很大
(配合今天fate新番特地用的贞德2333)
dp的题很多,但是很多时候dp并不是解题的全部,这样的题也见过不少了。这类题大多都是发现解题所需的条件具有线性关系,可以由dp的方式递推得到,作为预处理,这道题就是这样。
Description
给出n个只有重量没有价值的物品和一个大小为m的背包,现在要求选出一些物品放进背包(不能超过重量限制m),使得剩下的物品都不能再放进去,问不同的方案数有多少.如果两个方案中存在一件物品,在一个方案中放进去了,在另一个方案中没有放进去,那么这两个方案不同.1000组数据,n<=30,m<=1000
input
The input begins with a line containing an integer value specifying the number of datasets that follow, N (1 ≤ N ≤ 1000). Each dataset starts with a line containing two integer values V and D representing the number of vendors (1 ≤ V ≤ 30) and the dollar amount to spend (1 ≤ D ≤ 1000) respectively. The two values will be separated by one or more spaces. The remainder of each dataset consists of one or more lines, each containing one or more integer values representing the cost of a margarita for each vendor. There will be a total of V cost values specified. The cost of a margarita is always at least one (1). Input values will be chosen so the result will fit in a 32 bit unsigned integer.
output
For each problem instance, the output will be a single line containing the dataset number, followed by a single space and then the number of combinations for that problem instance.
首先分析一下题意。
剩下的都不能放进盒子里等价于找到一个重量最小的物品,所有比它小的都要在盒子里,比它大的物品把盒子装到不能把它装进去为止的方案数。
于是乎很自然的想到枚举最小的盒子,统计所有的答案。
假设当前枚举不放进去的重量最小的物品的重量为wi,重量比wi小的物品的重量之和为sum,重量比wi大的物品中选出的重量之和为val,那么sum+val < =m,sum+val+wi > m
也就是说,m-sum-wi < val<=m-sum
我们需要求出的就是用比当前枚举的物品重量大的所有物品中,选出几个来凑够val的方案数。好像是具有dp性质的。
我们需要求出”较大的i个物品组合出重量j的方案数”
对物品按照重量排序,定义f[i][j]表示最大的i个物品组合出重量j的方案数
f[i][j]=f[i-1][j]+f[i-1][j-w[i]]
复杂度O(nm*数据组数)
代码:
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
int tt,n,m,ans;
int w[55],f[55][1010],sum[55];
bool cmp(const int &a,const int &b)
{
return a>b;
}
int main()
{
scanf("%d",&tt);
for(int e=1;e<=tt;e++)
{
ans=0;
memset(w,0,sizeof(w));
memset(f,0,sizeof(f));
memset(sum,0,sizeof(sum));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
if(w[n]>m)//最小的物品也超过总容量,方案数为0
{
printf("%d 0\n",e);
continue ;
}
sort(w+1,w+n+1,cmp);//从大到小排序
for(int i=n;i>=1;i--)//后缀和
sum[i]=sum[i+1]+w[i];
for(int i=0;i<=n;i++) //从前i个凑出0的方案都是1,只能不取
f[i][0]=1;
for(int i=1;i<=n;i++) //DP
for(int j=w[i];j<=m;j++)
f[i][j]=f[i-1][j]+f[i-1][j-w[i]];
for(int i=n;i>=1;i--)
for(int j=max(m-sum[i+1]-w[i]+1,0);m>=sum[i+1]&&j<=m-sum[i+1];j++)
ans+=f[i-1][j];
printf("%d %d\n",e,ans);
}
return 0;
}
嗯,就是这样