【洛谷P4480】餐巾计划问题

题目

题目链接:https://www.luogu.com.cn/problem/P4480
双倍经验:https://www.luogu.com.cn/problem/P2223
三倍经验:https://www.luogu.com.cn/problem/P1251
四倍经验:https://www.luogu.com.cn/problem/P2917
一个餐厅在相继的 \(n\) 天里,每天需用的餐巾数不尽相同。假设第 \(i\)\((i=1, 2, ..., n)\) 需要 \(r_i\) 块餐巾。餐厅可以在任意时刻购买新的餐巾,每块餐巾的费用为 \(p\) 。使用过的旧餐巾,则需要经过清洗才能重新使用。把一块旧餐巾送到清洗店A,需要等待 \(m_1\) 天后才能拿到新餐巾,其费用为 \(c_1\) ;把一块旧餐巾送到清洗店B,需要等待 \(m_2\) 天后才能拿到新餐巾,其费用为 \(c_2\) 。例如,将一块第 \(k\) 天使用过的餐巾送到清洗店A清洗,则可以在第 \(k+m_1\) 天使用。
请为餐厅合理地安排好 \(n\) 天中餐巾使用计划,使总的花费最小。
\(n\leq 2\times 10^5\)

思路

弱化版是费用流解法。加强后考虑贪心。
首先如果设买 \(i\) 条毛巾所需要的总花费为 \(f(i)\),那么 \(f(i)\) 是一个单峰函数。当买的毛巾小于最优解时,洗毛巾的费用就会增加;多于最优解时,买毛巾的费用就会增加。
\(m_1\leq m_2\),如果此时 \(c_1\leq c_2\),那么显然第二种清洗店不会有任何作用,直接让 \(m_2=m_1,c_2=c_1\) 即可。
所以可以三分买毛巾的数量,然后考虑如何计算买 \(k\) 条毛巾时的最小花费。
对于其中一天,我们肯定是依次考虑这三种操作:

  • 如果有买了没用的毛巾,那么就立刻使用。显然往后留不会更优。
  • 如果有毛巾在 \(m_2\) 天前使用了,那么就使用这个毛巾。也就是能慢洗就慢洗。
  • 如果有毛巾在 \([m_1,m_2)\) 天前使用了,那么就使用这个毛巾。注意此时需要选择时间尽量后的毛巾使用,因为时间较前的毛巾可能再等待若干天就可以送去慢洗了。

那么维护三个队列 \(q1,q2,q3\),分别维护上一次使用时间在 \([1,m_1),[m_1,m_2),[m_2,+\infty)\) 内的毛巾。每当来到新的一天,就不断尝试将 \(q1\) 的队头扔到 \(q2\) 队尾,\(q2\) 队头扔到 \(q3\) 队尾。如果某一天所有毛巾都用完了还不够,那么返回 \(+\infty\) 就好。
时间复杂度 \(O(n\log (np))\)

代码

#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;

const int N=200010,Inf=2147483647;
int n,m1,m2,c1,c2,p,a[N];
pair<int,int> q1[N],q2[N],q3[N];

int solve(int mid)
{
	if (mid<0) return Inf;
	int h1=1,h2=1,h3=1,t1=0,t2=0,t3=0,ans=mid*p;
	for (int i=1;i<=n;i++)
	{
		for (;h1<=t1 && q1[h1].fi+m1<=i;h1++) q2[++t2]=q1[h1];
		for (;h2<=t2 && q2[h2].fi+m2<=i;h2++) q3[++t3]=q2[h2];
		int x=a[i],y=min(x,mid);
		if (mid) mid-=y,x-=y,q1[++t1]=mp(i,y);
		while (x && h3<=t3)
			if (q3[h3].se>x)
			{
				q3[h3].se-=x; ans+=x*c2;
				if (q1[t1].fi!=i) q1[++t1]=mp(i,x);
					else q1[t1].se+=x;
				x=0;
			}
			else
			{
				x-=q3[h3].se; ans+=q3[h3].se*c2;
				if (q1[t1].fi!=i) q1[++t1]=mp(i,q3[h3].se);
					else q1[t1].se+=q3[h3].se;
				h3++;
			}
		while (x && h2<=t2)
			if (q2[t2].se>x)
			{
				q2[t2].se-=x; ans+=x*c1;
				if (q1[t1].fi!=i) q1[++t1]=mp(i,x);
					else q1[t1].se+=x;
				x=0;
			}
			else
			{
				x-=q2[t2].se; ans+=q2[t2].se*c1;
				if (q1[t1].fi!=i) q1[++t1]=mp(i,q2[t2].se);
					else q1[t1].se+=q2[t2].se;
				t2--;
			}
		if (x) return Inf;
	}
	return ans;
}

int main()
{
	scanf("%d%d%d%d%d%d",&n,&m1,&m2,&p,&c1,&c2);
	m1++; m2++;
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	if (m1>=m2) swap(m1,m2),swap(c1,c2);
	if (c1<=c2) m2=m1,c2=c1;
	int l=1,r=20000000,mid;
	while (r-l>=3)
	{
		mid=(l+r)>>1;
		if (solve(mid)<solve(mid+1)) r=mid;
			else l=mid;
	}
	int ans=Inf;
	for (int i=l;i<=r;i++) ans=min(ans,solve(i));
	cout<<ans;
	return 0;
}
posted @ 2021-07-20 21:12  stoorz  阅读(113)  评论(0编辑  收藏  举报