【bzoj1920】[Ctsc2010]产品销售【模拟费用流】【线段树】

题目传送门
真心哭了,翻了全网居然找不到一篇题解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这条边。
增广路分两种:
【bzoj1920】[Ctsc2010]产品销售【模拟费用流】【线段树】_第1张图片
一种是从左往右走的,一种是从右往左走的。
由于我们是从左往右增广,所以第一种增广时右边的所有边都没有流量。
但是第二种增广路,可能会经过一些图上标红的路径,这些路径上的边是第一种增广路的反边。
因为反边的费用一定小于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;
}

你可能感兴趣的:(模拟费用流,线段树)