题解:P4181 [USACO18JAN] Rental Service S

在博客园食用更佳

我们发现,出租一头奶牛产生的利润与奶牛的产奶量 \(c\) 无关,所以我们可以考虑把产奶量低的奶牛拿出去出租,剩下的就去产奶卖钱。卖奶和出租始终考虑价高者得原则,即按照收购价格从大到小排序,依次分配给每一个订单。

具体地,我们将产奶量 \(c_i\) 从大到小排序,然后枚举一个 \(k\),让编号为 \(1 \sim k\) 的奶牛产奶,\(k+1 \sim n\) 的奶牛去出租,然后计算一下该方案的利润,最后将所有方案取一个利润最大值就是最终的答案。

【出租】:对于第 \(k+1 \sim n\) 头奶牛,可以按照出租的价格 \(r_i\) 从大到小依次分配给每个出租订单,这一部分可以用前缀和 \(O(1)\) 计算;

【卖奶】:对于第 \(1 \sim k\) 头奶牛,计算出这一部分奶牛产奶量的总和,然后用两个数组 \(sp_i,sq_i\) 表示\(sp_i\) 单位的牛奶可以赚 \(sq_i\) 的钱,这两个也可以用前缀和的思想 \(O(n)\) 预处理。

查询卖 \(k\) 的牛奶能赚多少钱的话可以参考初学 OI 时的分段收费问题,把 \(k\) 分成若干段,每一段对应一个订单的 \(p,q\),在 \(sq_i\) 里面二分查找一个第一个 \(\ge k\)\(sq_i\),然后就可以分开两半计算:一半是完整的若干段 \(sp_{i-1}\),另一半是多出来的 \(k-sq_{i-1}\) 单位的牛奶,这一部分的单价是 \(p_i\)。注意考虑刚好卖完一段和 \(i=0\) 的情况。

参考程序(注:优先队列可换成结构体排序的形式,因为之前写了个假的做法就是用的优先队列):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=1e5+7;
int n,m,r;
int c[N],b[N];
ll sc[N];  //c的前缀和 
struct request{int p,q;};
struct cmp{bool operator()(request A,request B){return A.p<B.p;}};
priority_queue<request,vector<request>,cmp> sell;
int p[N],q[N],rent[N];
ll sp[N],sq[N];  //卖sq[i]的牛奶能赚sp[i]的钱 
ll sr[N];  //rent的前缀和(绝对不是star rail) 
inline ll calc(ll k)  //卖k的牛奶能赚多少钱 
{
	int i=lower_bound(sq+1,sq+m+1,k)-sq;
//	cerr<<sp[i]<<' '<<sq[i]<<' '<<p[i]<<','<<i<<' '<<k<<endl;
	if(i==0) return k*p[1];
	if(sq[i]==k) return sp[i];
	else return sp[i-1]+(k-sq[i-1])*p[i];
}
int main()
{
//	freopen("neuvillette.in","r",stdin);
//	freopen("neuvillette.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>r;
	for(int i=1;i<=n;i++) cin>>c[i];
	sort(c+1,c+n+1,greater<int>());
	for(int i=1;i<=n;i++) sc[i]=sc[i-1]+c[i];
	for(int i=1,p,q;i<=m;i++)
	{
		cin>>q>>p;
		sell.push({p,q});
	}
	for(int i=1;i<=r;i++) cin>>rent[i];
	sort(rent+1,rent+r+1,greater<int>());
	for(int i=1;i<=n;i++) sr[i]=sr[i-1]+rent[i];
	for(int i=1;i<=m;i++)
	{
		p[i]=sell.top().p;
		q[i]=sell.top().q;
		sp[i]=sp[i-1]+sell.top().p*q[i];
		sq[i]=sq[i-1]+sell.top().q;
		sell.pop();
	}
	ll ans=-12180211;
	for(int k=1;k<=n;k++)
	{
		ans=max(ans,calc(sc[k])+sr[n-k]);
//		cerr<<k<<' '<<sc[k]<<' '<<calc(sc[k])<<' '<<sr[n-k]<<endl;
	}
	cout<<ans;
	cout.flush();
	return 0;
}
/*
把奶牛的产奶量从大到小排序完之后,考虑派出[1,k]的牛产奶,[k+1,n]的牛出租
然后计算价格,找出一个最优的方案出来 
出租的牛一共有(n-(k+1)+1)=n-k头 

以下做法是假的,以下做法是假的,以下做法是假的 
for i=1~n考虑当前牛是卖奶还是出租
如果出租带来的收益比卖奶要大那就卖奶,反之亦然
处理牛奶委托和出租委托的策略是按照价格从大到小排序 
*/
posted @ 2025-08-16 17:01  wwwidk1234  阅读(13)  评论(0)    收藏  举报