题解 P4480 【[BJWC2018]餐巾计划问题】
George1123 · · 题解
题解-[BJWC2018]餐巾计划问题
好好的网络流题,变成了队列加模拟。
参考资料
暂无
博客中原文
\color{#000}\texttt{Introduction}
蒟蒻在刷省选水题时发现一道 [HNOI2001]软件开发,与当年的网络流题 餐巾计划问题 一模一样。然后蒟蒻又看了讨论,这两题又和 [USACO08NOV]Toys G 和 [BJWC2018]餐巾计划问题 除了数据范围以外一模一样。然后这题就是其中数据范围最强的了,做法竟然是队列和模拟。
\color{#000}\texttt{Description}
[BJWC2018]餐巾计划问题
有
n 天,每天需要r_i 块干净餐巾给人用,用完后会脏。每天买新干净餐巾p 元每块,洗脏餐巾需要c_1 元每块耗时m_1 天或c_2 元每块耗时m_2 天。求最少花费。数据范围:
1\le n\le 2\times 10^5 ,1 \le m_1, m_2 \le n ,1 \le c_1, c_2, p \le 100 ,1 \le r_i \le 100 。
\color{#000}\texttt{Solution}
网络流的
首先从两种洗餐巾方式入手。把
买的餐巾总数
但是知道了
然后是能用慢洗不用快洗,很明显,因为快洗贵。
所以可以用三个双向队列维护脏餐巾(每天用完的餐巾)、慢洗完的餐巾(用完后至少
特别的,如果某天的要求怎么都满足不了,就返回
最后答案就是三分或斜率二分得到的耗钱谷点的耗钱数。
\color{#000}\texttt{Code}
#include <bits/stdc++.h>
using namespace std;
//&Start
#define lng long long
#define lit long double
const int inf=0x3f3f3f3f;
const lng Inf=1e17;
//&Check
const int N=2e5+10;
int n,r[N],fx,fm,fk,Tm,Tk,sm,ans=inf;
struct bag{int T,v;};
//买来的时间、数量(把多份一起买的餐巾打包)
deque<bag> qx,qm,qk;//新买、慢洗、快洗
int f(int x){//买x块餐巾计算最优耗钱
int res=x*fx;//买餐巾的钱
qx.clear(),qm.clear(),qk.clear();
for(int i=1;i<=n;i++){
while(qx.size()&&qx.front().T+Tk<=i)
qk.push_back(qx.front()),qx.pop_front();
//把旧餐巾快洗完的丢进快洗队列
while(qk.size()&&qk.front().T+Tm<=i)
qm.push_back(qk.front()),qk.pop_front();
//把快洗完的餐巾慢洗完的丢进慢洗队列
int nd=r[i],by=min(nd,x);//如果还可以买餐巾就先买
x-=by,nd-=by;
while(nd&&qm.size()){//先慢洗
by=min(nd,qm.back().v);
nd-=by,res+=by*fm;
if(by==qm.back().v) qm.pop_back();//用完了
else qm.back().v-=by;
}
while(nd&&qk.size()){//再快洗
by=min(nd,qk.back().v);
nd-=by,res+=by*fk;
if(by==qk.back().v) qk.pop_back();//用完了
else qk.back().v-=by;
}
if(nd) return inf;
qx.push_back((bag){i,r[i]});
}
return res;
}
//&Main
int main(){
scanf("%d%d%d%d%d%d",&n,&Tm,&Tk,&fm,&fk,&fx);
if(Tm<Tk) swap(Tm,Tk),swap(fm,fk);
if(fm>fk) Tm=Tk,fm=fk;//保证快洗快贵,慢洗慢便宜
for(int i=1;i<=n;i++) scanf("%d",r+i),sm+=r[i];
int l=0,r=sm+1;
while(l<r-1){//斜率二分
int mid=(l+r)>>1;
if(f(mid)<f(mid+1)) r=mid;
else l=mid;
}
printf("%d\n",f(r));//Go!
return 0;
}
然后把这份代码交到另外三题,就妥妥四倍经验了。我还是太蒻了。祝大家学习愉快!