题目传送门
真心哭了,翻了全网居然找不到一篇题解qaq
我们可以考虑一个简单的网络流建图。
S − > i : ( D i , 0 ) S->i:(D_i,0) S−>i:(Di,0)
i − > T : ( U i , P i ) i->T:(U_i,P_i) i−>T:(Ui,Pi)
i − > i + 1 : ( i n f , C i ) i->i+1:(inf,C_i) i−>i+1:(inf,Ci)
i + 1 − > i : ( i n f , M i ) i+1->i:(inf,M_i) i+1−>i:(inf,Mi)
显然直接跑网络流复杂度过高。
可以考虑模拟费用流,用线段树优化网络流。
有个很明显的结论:与源点和汇点相连的边不会被退流。
还有一个我不会证的性质:执行费用流时,每次选取一条必须流过的与源点相连的边开始增广也是可行的。
这题我们可以从小到大枚举 i i i,尝试增广 S − > i S->i S−>i这条边。
增广路分两种:
一种是从左往右走的,一种是从右往左走的。
由于我们是从左往右增广,所以第一种增广时右边的所有边都没有流量。
但是第二种增广路,可能会经过一些图上标红的路径,这些路径上的边是第一种增广路的反边。
因为反边的费用一定小于0,所以有反边的时候应该优先走从左往右的反边,再走从右往左的边。
注意到,从左往右的每条边开始的时候是总被走正边,一段时间后就只会被走反边,直到流量为0。
这意味着,从左往右的每条边的费用只会变化一次,从正变成负,变成负后就会成为反边。
因此,我们用两棵线段树分别维护从左到右和从右到左的费用,再用一棵线段树维护从左往右的每条边的流量。当有边的流量为0时,在第三棵线段树上找到这些边,直接在第二棵线段树修改从右往左的费用。线段树需要支持区间加法和区间极值。时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=100005,inf=0x7f7f7f7f;
int n,buy[N],make[N],cost[N],rl[N],lr[N],crl[N],clr[N],delta[N],sum[N];
ll ans;
namespace tree1{
int mnid[N*4];
int get(int x,int y){
return clr[x]<clr[y]?x:y;
}
void build(int o,int l,int r){
if(l==r){
mnid[o]=l;
return;
}
int mid=(l+r)/2;
build(o*2,l,mid);
build(o*2+1,mid+1,r);
mnid[o]=get(mnid[o*2],mnid[o*2+1]);
}
void del(int o,int l,int r,int k){
if(l==r){
return;
}
int mid=(l+r)/2;
if(k<=mid){
del(o*2,l,mid,k);
}else{
del(o*2+1,mid+1,r,k);
}
mnid[o]=get(mnid[o*2],mnid[o*2+1]);
}
int query(int o,int l,int r,int L,int R){
if(L==l&&R==r){
return mnid[o];
}
int mid=(l+r)/2;
if(R<=mid){
return query(o*2,l,mid,L,R);
}else if(L>mid){
return query(o*2+1,mid+1,r,L,R);
}else{
return get(query(o*2,l,mid,L,mid),query(o*2+1,mid+1,r,mid+1,R));
}
}
}
namespace tree2{
int ans,id,minn[N*4],mnid[N*4],tag[N*4];
void pushup(int o){
if(minn[o*2]<minn[o*2+1]){
minn[o]=minn[o*2];
mnid[o]=mnid[o*2];
}else{
minn[o]=minn[o*2+1];
mnid[o]=mnid[o*2+1];
}
}
void pushdown(int o){
if(tag[o]){
minn[o*2]+=tag[o];
tag[o*2]+=tag[o];
minn[o*2+1]+=tag[o];
tag[o*2+1]+=tag[o];
tag[o]=0;
}
}
void build(int o,int l,int r){
if(l==r){
minn[o]=crl[l];
mnid[o]=l;
return;
}
int mid=(l+r)/2;
build(o*2,l,mid);
build(o*2+1,mid+1,r);
pushup(o);
}
void update(int o,int l,int r,int L,int R,int v){
if(L<=l&&R>=r){
minn[o]+=v;
tag[o]+=v;
return;
}
pushdown(o);
int mid=(l+r)/2;
if(L<=mid){
update(o*2,l,mid,L,R,v);
}
if(R>mid){
update(o*2+1,mid+1,r,L,R,v);
}
pushup(o);
}
void del(int o,int l,int r,int k){
if(l==r){
minn[o]=inf;
return;
}
pushdown(o);
int mid=(l+r)/2;
if(k<=mid){
del(o*2,l,mid,k);
}else{
del(o*2+1,mid+1,r,k);
}
pushup(o);
}
void query(int o,int l,int r,int L,int R){
if(L<=l&&R>=r){
if(minn[o]<ans){
ans=minn[o];
id=mnid[o];
}
return;
}
pushdown(o);
int mid=(l+r)/2;
if(L<=mid){
query(o*2,l,mid,L,R);
}
if(R>mid){
query(o*2+1,mid+1,r,L,R);
}
}
}
namespace tree3{
int ans,minn[N*4],tag[N*4];
void pushdown(int o){
if(tag[o]){
minn[o*2]+=tag[o];
tag[o*2]+=tag[o];
minn[o*2+1]+=tag[o];
tag[o*2+1]+=tag[o];
tag[o]=0;
}
}
void build(int o,int l,int r){
if(l==r){
minn[o]=inf;
return;
}
int mid=(l+r)/2;
build(o*2,l,mid);
build(o*2+1,mid+1,r);
minn[o]=min(minn[o*2],minn[o*2+1]);
}
void update(int o,int l,int r,int k,int v){
if(l==r){
minn[o]=v;
return;
}
pushdown(o);
int mid=(l+r)/2;
if(k<=mid){
update(o*2,l,mid,k,v);
}else{
update(o*2+1,mid+1,r,k,v);
}
minn[o]=min(minn[o*2],minn[o*2+1]);
}
void update(int o,int l,int r,int L,int R,int v){
if(L<=l&&R>=r){
minn[o]+=v;
tag[o]+=v;
return;
}
pushdown(o);
int mid=(l+r)/2;
if(L<=mid){
update(o*2,l,mid,L,R,v);
}
if(R>mid){
update(o*2+1,mid+1,r,L,R,v);
}
minn[o]=min(minn[o*2],minn[o*2+1]);
}
void query(int o,int l,int r,int L,int R){
if(L<=l&&R>=r){
ans=min(ans,minn[o]);
return;
}
pushdown(o);
int mid=(l+r)/2;
if(L<=mid){
query(o*2,l,mid,L,R);
}
if(R>mid){
query(o*2+1,mid+1,r,L,R);
}
}
void modify(int o,int l,int r,int L,int R){
if(minn[o]){
return;
}
int mid=(l+r)/2;
if(L<=l&&R>=r){
if(l==r){
minn[o]=inf;
tree2::update(1,1,n,1,l,delta[l]);
return;
}
pushdown(o);
if(!minn[o*2]){
modify(o*2,l,mid,L,R);
}
if(!minn[o*2+1]){
modify(o*2+1,mid+1,r,L,R);
}
}else{
pushdown(o);
if(L<=mid){
modify(o*2,l,mid,L,R);
}
if(R>mid){
modify(o*2+1,mid+1,r,L,R);
}
}
minn[o]=min(minn[o*2],minn[o*2+1]);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&buy[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&make[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&cost[i]);
}
for(int i=1;i<n;i++){
scanf("%d",&rl[i]);
delta[i]+=rl[i];
}
for(int i=n-1;i>0;i--){
rl[i]+=rl[i+1];
crl[i]=rl[i]+cost[i];
}
crl[n]=cost[n];
for(int i=2;i<=n;i++){
scanf("%d",&lr[i]);
delta[i-1]+=lr[i];
lr[i]+=lr[i-1];
clr[i]=lr[i]+cost[i];
}
clr[1]=cost[1];
tree1::build(1,1,n);
tree2::build(1,1,n);
tree3::build(1,1,n);
for(int i=1;i<=n;i++){
for(sum[i]+=sum[i-1];buy[i];){
int mnid1=tree1::query(1,1,n,i,n);
int mn1=clr[mnid1]-lr[i];
tree2::ans=inf;
if(i!=1){
tree2::query(1,1,n,1,i-1);
}
int mnid2=tree2::id;
int mn2=tree2::ans-rl[i];
if(mn1<mn2){
int tmp=min(buy[i],make[mnid1]);
ans+=1LL*tmp*mn1;
sum[i]+=tmp;
sum[mnid1]-=tmp;
buy[i]-=tmp;
make[mnid1]-=tmp;
if(!make[mnid1]){
clr[mnid1]=inf;
tree1::del(1,1,n,mnid1);
tree2::del(1,1,n,mnid1);
}
}else{
tree3::ans=inf;
tree3::query(1,1,n,mnid2,i-1);
int tmp=min(buy[i],min(make[mnid2],tree3::ans));
ans+=1LL*tmp*mn2;
buy[i]-=tmp;
make[mnid2]-=tmp;
if(!make[mnid2]){
clr[mnid2]=inf;
tree1::del(1,1,n,mnid2);
tree2::del(1,1,n,mnid2);
}
tree3::update(1,1,n,mnid2,i-1,-tmp);
if(tmp==tree3::ans){
tree3::modify(1,1,n,mnid2,i-1);
}
}
}
if(sum[i]){
tree3::update(1,1,n,i,sum[i]);
tree2::update(1,1,n,1,i,-delta[i]);
}
}
printf("%lld\n",ans);
return 0;
}