单调队列优化的多重背包。
花了一周时间才把单调队列看懂。
是在太难理解了,而且网上资料讲了都是理论,理解起来真的好难,只能每天看一次慢慢就懂了。
/*
题意:有若干不同面值的硬币,问能组合出1到m中的几种面值?
思路:多重背包+单调队列优化(二进制优化会TLE)
dp[i]=true表示能够用所给的硬币凑到面值i
*/
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN=100005;
struct Node
{
int a;//价值
int c;//数量
};
Node coins[105];
bool dp[MAXN],drabqueue[MAXN];//dp数组,单调队列数组
void CompletePack(int w,int V)//完全背包,w:价值也是代价,V:背包容量
{
for(int i=w;i<=V;i++)//顺序求解
{
if(dp[i-w]&&(!dp[i]))
{
dp[i]=true;
}
}
}
void ZeroOnePack(int w,int V)//01背包,w:价值也是代价,V:背包容量
{
for(int i=V;i>=w;i--)//逆序求解
{
if(dp[i-w]&&(!dp[i]))
{
dp[i]=true;
}
}
}
void MuiltPack(int w,int c,int V)
{
if(c==1)//数量为1 ->01背包
{
ZeroOnePack(w,V);
}
else if(w*c>=V)//数量*价值>=背包容量,相当于有无限多
{ //的数量可以用 ->完全背包
CompletePack(w,V);
}
else//多重背包+单调队列优化
{
for(int i=0;i<w;i++)//枚举余数(0<=i<w)
{
int start=0,end=-1,sum=0;
//单调队列的开始和结尾, sum队列中是否有一个为真
for(int j=i;j<=V;j+=w)
{
if(end-start==c)//队列已满
{
sum-=drabqueue[start++];//队首元素出队
}
drabqueue[++end]=dp[j];//新元素加入队尾
sum+=dp[j];//累加新元素
if((!dp[j])&&sum)
{
dp[j]=true;
}
}
}
}
}
int main()
{
int n,m;
while(cin>>n>>m)
{
if(!n&&!m)
{
break;
}
memset(dp,0,sizeof(dp));//初始化
memset(drabqueue,0,sizeof(drabqueue));//初始化
dp[0]=true;//初始化
for(int i=1;i<=n;i++)
{
cin>>coins[i].a;//每种硬币的价值
}
for(int i=1;i<=n;i++)
{
cin>>coins[i].c;//每种硬币的数量
}
for(int i=1;i<=n;i++)
{
MuiltPack(coins[i].a,coins[i].c,m);
}
int ans=0;//标记变量,累加能够凑到的硬币数量
for(int i=1;i<=m;i++)
{
if(dp[i])//若可用所给的硬币凑到i。
{
ans++;
}
}
cout<<ans<<endl;
}
return 0;
}