模拟费用流学习笔记

模拟费用流学习笔记

就是用各种东西维护费用流,有退流的可以理解为反悔贪心,否则直接贪心。

来点典题

uoj445 UER#8 B

先让每个送餐员匹配左边的最优的餐厅,这个过程可以用堆维护餐厅的 \(w-y\) 的最小值。

匹配右边的情况考虑反悔,让右边的餐厅匹配送餐员,再开个堆维护 \(-v-x\)\(v\)为这个送餐员的最优匹配。然后找到餐厅之后就在这个堆里找一个匹配,这个也要反悔,在餐厅的堆里加入 \(-v+(w-y)\)

还有用后面的餐厅匹配前面已经匹配的送餐员的情况,在送餐员的堆中加入\(-w-y\)

还有用后面的送餐员匹配前面已经匹配的餐厅的情况,这种一定不会更优。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i,a,b) for(ll i=a;i<=b;++i)
ll read(){
	ll x=0,pos=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
	for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
	return pos?x:-x;
} 
const ll  N = 2e5+200;
ll ans;
ll n,m;
ll x[N],y[N],w[N],c[N];
struct node{
	ll w;ll cnt;
	node(ll w=0,ll cnt=0):w(w),cnt(cnt){}
};
struct cmp{
	ll operator()(node a,node b){
		return a.w>b.w;
	}
};
priority_queue<node,vector<node>,cmp> q1,q2;
ll inf = 2e12;
void work1(ll now){
	ll res=inf;
	if(!q1.empty()){
		node fr=q1.top();q1.pop();
		res=fr.w+x[now];
		if((--fr.cnt)>0) q1.push(fr);
	}
	ans+=res;
	q2.push(node(-res-x[now],1));
}
void work2(ll now){
	ll rc=c[now];
	while(!q2.empty()&&rc>0){
		ll nw=q2.top().w+w[now]+y[now];
		if(nw>=0) break;
		if(rc<=q2.top().cnt){
			ans+=nw*rc;
			q1.push(node(-nw+w[now]-y[now],rc));
			node fr=q2.top();fr.cnt-=rc;rc=0;q2.pop();if(fr.cnt!=0) q2.push(fr);
			break;
		}else{
			rc-=q2.top().cnt;
			ans+=nw*q2.top().cnt;
			q1.push(node(-nw+w[now]-y[now],q2.top().cnt));
			q2.pop();
		}
	}
	if(c[now]>rc){
		q2.push(node(-w[now]-y[now],c[now]-rc));
	}
	if(rc){
		q1.push(node(w[now]-y[now],rc));
	}
}
int main(){
	n=read(),m=read();
	FOR(i,1,n) x[i]=read();
	ll tot=0;
	FOR(i,1,m){
		y[i]=read(),w[i]=read(),c[i]=read();tot+=c[i];
	}
	if(tot<n){
		printf("-1");
		return 0;
	}
	for(ll i=1,j=1;i<=n||j<=m;){
		if(i<=n&&(j>m||x[i]<y[j])){
			work1(i++);
		}else work2(j++);
	}
	printf("%lld\n",ans);
	return 0;
}

loj6405. 「ICPC World Finals 2018」征服世界

在LCA合并,添加反悔。可以发现不会同时反悔,所以写起来很方便。

注意一个都不匹配是最优的,而题目要求是所有军队都匹配,所以先把每个匹配的权值减去 \(\inf\) 就能让匹配数优先级大于权值。最后还要加上 \(\sum y_i*\inf\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i,a,b) for(ll i=a;i<=b;++i)
ll read(){
	ll x=0,pos=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
	for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
	return pos?x:-x;
} 
const ll  N = 2.5e5+200;
ll n,m;
ll x[N],y[N],w[N],c[N];
struct node{
	mutable ll w;ll cnt;
	node(ll w=0,ll cnt=0):w(w),cnt(cnt){}
	int operator <(node b){
		return w<b.w;
	}
}val[N*30];
int son[N*30],nex[N*30],num=0;
struct heap{
	int merge(int u,int v){
		if(!u||!v) return u+v;
		if(val[v]<val[u]) swap(u,v);nex[v]=son[u];son[u]=v;return u;
	}
	int merges(int u){
		if(!u) return 0;if(!nex[u]) return u;
		int v1=nex[u],v2=nex[v1];
		nex[u]=nex[v1]=0;return merge(merge(u,v1),merges(v2));
	}
	int rt;
	void pop(){rt=merges(son[rt]);}
	void join(int x){rt=merge(rt,x);}
	node &top(){return val[rt];}
	void push(node now){val[++num]=now;rt=merge(rt,num);}
	int empty(){return rt==0;}
}H[N*2],M[N*2];
#define pii pair<int,int>
#define fi first
#define se second
vector<pii>G[N];
const ll inf = 1e12;
ll ans=0;
void dfs(int now,int pre,ll dep){
	H[now].push(node(dep,x[now]));
	M[now].push(node(dep-inf,y[now]));
	FOR(i,0,(int)G[now].size()-1){
		int v=G[now][i].fi;if(v==pre) continue;
		dfs(v,now,dep+G[now][i].se);
		H[now].join(H[v].rt);
		M[now].join(M[v].rt);
	}
	while(!H[now].empty()&&!M[now].empty()){
		node &a=H[now].top(),&b=M[now].top();
		ll nw=a.w+b.w-2*dep;
		int cnt=min(a.cnt,b.cnt);
		if(nw>=0) break;
		ans+=nw*cnt;
		a.cnt-=cnt,b.cnt-=cnt;if(!a.cnt) H[now].pop();if(!b.cnt) M[now].pop();
		H[now].push(node(-nw+a.w,cnt));
		M[now].push(node(-nw+b.w,cnt));
	}
} 
int main(){
	n=read();
	FOR(i,1,n-1){
		int u=read(),v=read(),c=read();
		G[u].push_back(make_pair(v,c));
		G[v].push_back(make_pair(u,c));
	}
	ll sum=0;
	FOR(i,1,n){
		x[i]=read(),y[i]=read();sum+=y[i]; 
	}
	dfs(1,0,0);
	printf("%lld\n",ans+sum*inf);
	return 0;
}

NOI2017 蔬菜

首先有个特别考思维的题意转化费用流,然后贪心地用堆维护就行了,甚至不需要反悔。最后还有一个思维点,没有退流,可以转化成递推。

posted @ 2021-02-04 16:14  lcyfrog  阅读(68)  评论(0编辑  收藏