多重背包问题。
详解见博客:
https://blog.youkuaiyun.com/Estia_/article/details/83590060
这道题时间卡得超严,在看大佬的代码之前疯狂TLE。
代码如下:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
bool dp[maxn]; //用bool快超多
int a0[105];
int main()
{
int n,m,c;
while(~scanf("%d%d",&n,&m)&&n&&m)
{
for(int i=1;i<=m;i++) //原本i,j,k等都是在一开始定义好的,但疯狂TLE,就把它们弄成临时定义,结果好像快了一点
dp[i]=0;
dp[0]=1;
for(int i=1;i<=n;i++)
scanf("%d",&a0[i]);
for(int i=1;i<=n;i++)
{
scanf("%d",&c);
if(a0[i]*c>=m) //转化为完全背包
for(int j=a0[i];j<=m;j++)
dp[j]=dp[j]|dp[j-a0[i]];
else //转化为01背包
{
for(int k=1;k<=c;k<<=1)
{
int val=a0[i]*k;
for(int j=m;j>=val;j--)
dp[j]=dp[j]|dp[j-val];
c-=k;
if(!c)
break;
}
if(c)
{
int val=a0[i]*c;
for(int j=m;j>=val;j--)
dp[j]=dp[j]|dp[j-val];
}
}
}
int sum=0;
for(int i=1;i<=m;i++)
sum+=dp[i];
printf("%d\n",sum);
}
return 0;
}
其实后来一想,原题的题意理解错了,题目是要求问1~m之间有多少可以构成多少种价值,而不是说构成价值1-m之间的方案数有多少种。(虽然最终结果都一样)也因此,dp数组可以用成bool类型。
事实上,这道题也属于另外一种类型题,多重背包可行性分析,对于这类问题,还有一种O(n*m)的算法。
强推:http://www.doc88.com/p-2961204836432.html
状态:dp[i,j]表示用前i中物品构成价值j时,第i种硬币最多剩下多少种。
如果能够构成价值j,则0<=do[i,j]<=c[i],否则dp[i,j]=-1.
对于这个算法,每一步都要进行初始化:
if(d[i-1,j]>=0) dp[i,j]=c[i]
else dp[i,j]=-1;
状态转移方程:
for(j=0,j<=m-a[i],;j++)
if(dp[i,j]>0)
dp[i,j+a[i]]=max(dp[i,j+a[i]],dp[i,j]-1);
这些会在代码中解释。
代码如下:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int dp[maxn]; //这里要用成一维数组,否则会爆内存(POJ这道题卡得真的是严)
int a0[105];
int main()
{
int n,m,c;
while(~scanf("%d%d",&n,&m)&&n&&m)
{
for(int i=1;i<=m;i++) //最初的初始化
dp[i]=-1;
dp[0]=0;
for(int i=1;i<=n;i++)
scanf("%d",&a0[i]);
for(int i=1;i<=n;i++)
{
scanf("%d",&c);
for(int j=0;j<=m;j++)
if(dp[j]>=0) dp[j]=c; //相当于dp[i-1,j]>=0,即用前i-1种硬币已经能够构成价值j了,那么也就用不着第i种硬币了,故dp[i,j]=c
else dp[j]=-1; //否则,先把那些用前i-1种硬币无法构成的价值,都先赋值为-1
for(int j=0;j<=m-a0[i];j++)
if(dp[j]>0) //如果dp[i,j]>0,说明这种价值j的方案存在而且第i种硬币还有剩余
dp[j+a0[i]]=max(dp[j+a0[i]],dp[j]-1); //那么更新dp[i,j+a0[i]],注意,原本dp[i,j+a0[i]]是可能为-1的,这一步就将前面用前i-1种硬币无法构成的价值当中,再次挑出那些可以由于第i种硬币的参与,而能够构成的价值
}
int sum=0;
for(int i=1;i<=m;i++)
if(dp[i]>=0) //价值为j的方案存在
sum++;
printf("%d\n",sum);
}
return 0;
}
个人觉得,这种算法一个核心就是用前面已经能够构成的价值方案,去推出更多的价值。