Codeforces 808 E. Selling Souvenirs(三分)

E. Selling Souvenirs

题意:
n件物品,有重量和价值,重量只有三种1,2,3。问取不超过m重量的物品的价值总和最大是多少。(n<=1e5,w<=3e5)
思路:
n*w很大,常规01背包不能直接做。考虑到物品重量不超过3,可以按重量分类物品。枚举重量3的物品取的件数,可以发现如果重量2的物品取得多则重量1的物品就得取得少,反之亦然。因此所取重量1,2的物品的价值和与重量2物品取的件数应该满足一个上凸函数的关系,即有一个极值点,那个点就是当前枚举下的1,2物品最大价值和。因此枚举重量3的物品件数,三分重量2的物品件数可以解决这个问题。
代码:

#include<bits/stdc++.h>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<"
"
#define sz(x) int(x.size())
#define All(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> P;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=1e5+10,mod=1e9+7,INF=0x3f3f3f3f;
vector<int> v[5];
ll sum[5][maxn]; 
ll f(int w,int n)
{
	return sum[2][n]+sum[1][min(sz(v[1]),w-2*n)];
}
int main()
{
	int n,m;
	cin>>n>>m;
	for (int i=0;i<n;++i)
	{
		int w,c;
		scanf("%d%d",&w,&c);
		v[w].pb(c);
	}
	for (int i=1;i<=3;++i)
	{
		sort(All(v[i]),[&](int a,int b){return a>b;});
		for (int j=1;j<=sz(v[i]);++j)
			sum[i][j]=sum[i][j-1]+v[i][j-1];
	}
	ll ans=0;
	for (int i=0;i<=sz(v[3])&&3*i<=m;++i)
	{
		int rest=m-3*i,l=0,r=min(sz(v[2]),rest/2);
		ans=max(ans,max(f(rest,l),f(rest,r))+sum[3][i]);
		while (r-l>1)
		{
			int lmid=(l+r)>>1,rmid=(lmid+r)>>1;
			if (f(rest,lmid)<f(rest,rmid))
				l=lmid;
			else
				r=rmid;
		}
		ans=max(ans,max(f(rest,l),f(rest,r))+sum[3][i]);
	}
	cout<<ans;
	return 0;
}
原文地址:https://www.cnblogs.com/orangee/p/10328414.html