HenryHuang
2019-12-18 11:25:58
刷了n次用了奇淫技巧才拿到rk1,亥
这道题是网络流二十四题中「餐巾计划问题」的加强版。
于是怀着试一试的心情用费用流交了一发:
哇塞,过了9个点!(强烈谴责出题人用*造数据
下面是费用流解法简述:
那么我们把每一天拆为早上和晚上两个点,设为
首先可以人为地规定一点:对一块餐巾,我们要么在其脏了之后立即送洗,要么买一块新干净餐巾来代替它。
然后对于每一个操作,我们可以如下连边:
对于买新干净餐巾:我们可以视作从源点买餐巾,从
对于送慢洗部:从
对于送快洗部:从
但是我们注意到,可能存在某一天的干净餐巾冗余。
于是我们要从
如何保证每天刚好只用
将其拆成两个板块:
从
从
优化无果,于是猜想可以不用费用流。
首先无论餐巾是最开始一起买还是需要用时再买,不会影响最终的答案。
那么如果我们已经确定了要买的新餐巾的张数,那么是否可以确定一种唯一的方案使得总花费最小呢?
不难得到有这样一种贪心策略:
在能够用新餐巾的时候,尽量使用新餐巾。
设
如果无新餐巾可用,则倒回到
如果没有
这样做使得时间线较远的旧餐巾更有可能慢洗。
如果慢洗比快洗贵,那么直接将慢洗的时间和价格都改为快洗的时间和价格即可。
朴素代码无O2只能过掉70分。
于是我怀疑单次判断的时间复杂度过高。有的题解里说是
于是我们可以优化常数。
注意到在如果在第
所以我们每次将第
这样可以保证在计算慢洗的时候一定是线性的,但仍然不能保证计算快洗部分时为线性。
最后我们需要确定最优解时需购买餐巾的张数
设最优解时最小代价为
则当
当
所以对于最优解
个人认为代码可读性还是挺高的,可以康康代码:
/*---Author:HenryHuang---*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=5e5+5;
const ll inf=1ll<<50;
ll sumr=0;
ll r[maxn],res[maxn];
ll n,m1,m2,c1,c2,p;
ll f(ll num){
ll ans=1ll*num*p;//提前算好新餐巾代价
ll now=0;
for(ll i=1;i<=n;++i){
res[i]=0;
if(r[i]){
ll tmp=r[i];
if(num){
ll k=min(tmp,num);
tmp-=k,res[i]+=k,num-=k;
if(!tmp) continue;
}//直接用新的
for(ll j=now;j<i-m2;++j){
res[j+1]+=res[j],res[j]=0;
}//累加旧洗餐巾
now=max(now,i-m2);
if(now<i&&res[now]){
ll k=min(tmp,res[now]);
res[now]-=k,res[i]+=k;
tmp-=k,ans+=1ll*k*c2;
if(!tmp) continue;
}//慢洗
for(ll j=i-m1;j>=1&&j>i-m2;--j){
if(res[j]){
ll k=min(tmp,res[j]);
res[j]-=k,res[i]+=k;
tmp-=k,ans+=1ll*k*c1;
}
if(!tmp) break;
}//快洗
if(tmp) return inf;
}
}
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m1>>m2>>c1>>c2>>p;
if(m1>m2) swap(m1,m2),swap(c1,c2);//1快2慢
if(c1<c2) c2=c1,m2=m1;
for(ll i=1;i<=n;++i)
cin>>r[i],sumr+=r[i];
ll l=1,r=max(10000ll,sumr/3);//奇淫技巧
ll ans=inf;
while(r-l>2){
ll k=(r-l)/3;
ll mid1=l+k,mid2=r-k;
ll aa=f(mid1),bb=f(mid2);
if(aa<bb) ans=min(ans,aa),r=mid2;
else ans=min(ans,bb),l=mid1;
}
for(ll i=l;i<=r;++i) ans=min(ans,f(i));
cout<<ans<<'\n';
return 0;
}