『动态规划·01背包』超市购物

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/89671708

题目描述

在这里插入图片描述
在这里插入图片描述

题解

这种比较特殊的 01 01背包 还是没有做到过唉。。

这道题我们观察数据,发现 v i v_i 的具体数字很小,但是背包的花费却很大,我们可以考虑用另外一张方式进行背包:用价值作为状态,用花费作为具体的 D P DP 值进行计算。

求在体积限制为代价最大,等价于在相同的价值下体积最小:即用最小的体积买到最大的代价。

为了更加方便统计答案,我们对这些店进行排序:按照每一家店的时间来进行排序。

我们设 f [ i ] [ j ] f[i][j] 表示前i家店,购买出价值为 j j 的最小花费,转移和正常的01背包相似:
f [ i ] [ j ] = m i n ( f [ i 1 ] [ j ] , f [ i v [ i ] ] + c [ i ] ) f[i][j]=min(f[i-1][j],f[i-v[i]]+c[i])
v [ i ] v[i] 表示价值, c [ i ] c[i] 表示花费。

这一步完成后,我们再改变一下状态:

  • f [ i ] [ j ] f[i][j] 表示前 i i 个商店,价值至少为 j j 的最小花费。
  • 那么就有: f [ i ] [ j ] = m i n ( f [ i ] [ k ] ) , j k n f[i][j]=min(f[i][k]),j\leq k\leq n
  • 这样就保证了答案是单调不上升的。

那么最后的步骤,我们可以使用两边二分查找来进行:

  • 第一遍:找到能够购买的最后一家店 p o s pos
  • 第二遍:在 f [ p o s ] f[pos] 这个一位数组上进行二分查找。找到第一个比 M M 要大的下表,然后再 1 -1 ,这就是我们所要找的答案。

这种方法十分巧妙,有两个程序。第一个是使用 S T L STL 的,第二个是手写二分答案的。

代码1:

#include <bits/stdc++.h>

using namespace std;

int n,m;
int t[301];
int v[301];
long long c[301];
long long f[301][90001];

void Sort(void)
{
	for (int i=1;i<n;++i)
	    for (int j=i+1;j<=n;++j)
	        if (t[i]>t[j]) 
	        {
	        	swap(t[i],t[j]);
	        	swap(v[i],v[j]);
	        	swap(c[i],c[j]);
	        }
	return;
}

int main(void)
{
	freopen("market.in","r",stdin);
	freopen("market.out","w",stdout);
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;++i) 
		scanf("%lld %d %d",c+i,v+i,t+i);
	Sort();
	int V = n*300;
	for (int i=1;i<=V;++i) f[0][i] = 1e15;
    for (int i=1;i<=n;++i)
        for (int j=0;j<=V;++j)
        {
        	f[i][j] = f[i-1][j];
        	if (j-v[i] >= 0) f[i][j] = min(f[i][j], f[i-1][j-v[i]]+c[i]);
        }
    for (int i=1;i<=n;++i)
        for (int j=V-1;j>=0;--j)
            f[i][j] = min(f[i][j+1], f[i][j]);
    //f[i][j] 表示 前i件物品 价值至少为j的最小代价
	for (int i=1,T,M;i<=m;++i)
	{
		scanf("%d %d",&T,&M);
		int pos = upper_bound(t+1,t+n+1,T)-t-1;// T <= t[pos]
		int ans = upper_bound(f[pos]+1,f[pos]+V+1,M)-f[pos]-1;// m <= f[pos][ans]
		printf("%d\n",ans);
	}
	return 0;
}

代码2:

#include <bits/stdc++.h>

using namespace std;

int n,m;
int t[301];
int v[301];
long long c[301];
long long f[301][90001];

void Sort(void)
{
	for (int i=1;i<n;++i)
	    for (int j=i+1;j<=n;++j)
	        if (t[i]>t[j]) 
	        {
	        	swap(t[i],t[j]);
	        	swap(v[i],v[j]);
	        	swap(c[i],c[j]);
	        }
	return;
}

int find(int x)
{
	//找到时间小于等于x的最大值 
	int l = 1, r = n;
	while (l <= r)
	{
		int mid = l+r >> 1;
		if (x >= t[mid]) l = mid+1;
		else r = mid-1;
	}
	if (x >= t[r]) return r;
	else return l;
}

int find2(int t,int x)
{
	//找到前t件物品中:花费小于等于x的最大价值 
	#define F(i) f[t][i]
	int l = 1,r = n*300;
	while (l <= r)
	{
		int mid = l+r >> 1;
		if (x >= F(mid)) l = mid+1;
		else r = mid-1;
	}
	if (x >= F(r)) return r;
	else return l;
}

int main(void)
{
	freopen("market.in","r",stdin);
	freopen("market.out","w",stdout);
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;++i) 
		scanf("%lld %d %d",c+i,v+i,t+i);
	Sort();
	int V = n*300;
	for (int i=1;i<=V;++i) f[0][i] = 1e15;
    for (int i=1;i<=n;++i)
        for (int j=0;j<=V;++j)
        {
        	f[i][j] = f[i-1][j];
        	if (j-v[i] >= 0) f[i][j] = min(f[i][j], f[i-1][j-v[i]]+c[i]);
        }
    for (int i=1;i<=n;++i)
        for (int j=V-1;j>=0;--j)
            f[i][j] = min(f[i][j+1], f[i][j]);
    //f[i][j] 表示 前i件物品 价值至少为j的最小代价
	for (int i=1,T,M;i<=m;++i)
	{
		scanf("%d %d",&T,&M);
		int pos = find(T);
		int ans = find2(pos,M);// m <= f[pos][ans]
		printf("%d\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/89671708