暑假-动态规划 III-(V - Coins)

单调队列优化的多重背包。

花了一周时间才把单调队列看懂。

是在太难理解了,而且网上资料讲了都是理论,理解起来真的好难,只能每天看一次慢慢就懂了。

/*
题意:有若干不同面值的硬币,问能组合出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;
}


你可能感兴趣的:(动态规划)