模拟费用流模型总结

基础模型

你在玩一款益智推箱子游戏。

数轴上有 n n n个箱子和 m m m个传送点,第 i i i个箱子在 x i x_i xi,第 i i i个传送点在 y i y_i yi。当箱子放在传送点上时可以传送走,每个传送点可以最多送走一个箱子,传送走所有箱子视为通关。

你可以做的唯一操作是将一个箱子向左推一个单位,耗费代价1,过程中允许有多个箱子在同一位置或者箱子在传送点位置但没被送走,问你通关的最小代价。

只要从左到右依次考虑每个物件(传送点或箱子),然后将箱子送入最右边还未被使用的传送点即可,用一个栈就能完成。

变形1.激活传送点

你在开始推箱子前,需要选择若干个传送点激活,只有激活了的传送点才能使用,激活第 i i i个传送点的代价是 w i w_i wi。箱子只能往左推,推一个单位花费1的代价。

不一定要传送走所有箱子,最大化代价。

依然从左往右考虑每个物件。

若物件是一个箱子 a a a,推入传送点 b b b产生代价是 x a − y b + w b x_a-y_b+w_b xayb+wb,所以要取 − y b + w b -y_b+w_b yb+wb最大的传送点,用堆维护传送点即可。若 x a − y b + w b x_a-y_b+w_b xayb+wb一定小于0,则不使用该箱子。

但是这只是个只顾眼前利益的贪心而已,可能最优的解是 a a a之后的一个箱子 c c c和传送点 b b b匹配, a a a不匹配呢?如果要更改 b b b匹配的箱子的话,应该要在答案里减去箱子 a a a造成的贡献,即相当于添加一个价值为 − x a -x_a xa的箱子。

取消 a a a b b b的匹配,改为 c c c b b b的匹配,这很像一个费用流的退流操作(事实上后面的模型会更像,因为这个还只是退流一次,没有什么 c c c抢走 b b b a a a就去匹配它本来匹配过的 d d d,然后现在匹配 d d d的节点又去……这种),实际上模拟费用流的本质就是用其他东西来模拟退流。

bzoj4977 跳伞求生

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
int n,m;LL ans;
struct node{int x,c;}d[200005];
bool cmp(node A,node B) {return A.x==B.x?A.c==-1:A.x<B.x;}
priority_queue<int> q;

int main()
{
	n=read(),m=read();
	for(RI i=1;i<=n;++i) d[i].x=read(),d[i].c=-1;
	for(RI i=1;i<=m;++i) d[n+i].x=read(),d[n+i].c=read();
	sort(d+1,d+1+n+m,cmp);
	for(RI i=1;i<=n+m;++i) {
		if(d[i].c==-1) {
			if(q.empty()||q.top()+d[i].x<=0) continue;
			ans+=q.top()+d[i].x,q.pop(),q.push(-d[i].x);
		}
		else q.push(d[i].c-d[i].x);
	}
	printf("%lld\n",ans);
	return 0;
}

变形2.必须传送掉

在任何一种条件组合的基础上,箱子必须被传送掉,最小化代价。

那么先让每个箱子都与一个激活代价为 I N F INF INF的传送点匹配,这个匹配是不可退流的。

变形3.左右都可走

箱子必须匹配一个传送点,传送点最多匹配一个箱子,传送点有激活代价,箱子可以向左推也可以向右推。

同样从左往右考虑每个物件。

若是一个箱子 a a a,在传送点堆中找一个代价最小的传送点 b b b,假设代价为 v b v_b vb,那么现在产生的贡献是 x a + v b x_a+v_b xa+vb

考虑后续有一个箱子要抢夺传送点 b b b,理论上应该加一个代价为 − x a -x_a xa的传送点,不过因为箱子都必须匹配,所以完成 a a a b b b的匹配时其实是除去了一个 I N F INF INF a a a本来和代价 I N F INF INF的虚拟传送点匹配的代价,所以应该加的是代价为 I N F − x a INF-x_a INFxa的传送点,也就不用加这个传送点了。

考虑后续有一个传送点要抢夺箱子 a a a,除去贡献,添加一个价值为 − v b − 2 x a -v_b-2x_a vb2xa的箱子。

若是一个传送点 b b b,在箱子堆中找一个代价最小的箱子 b b b,设代价为 v b v_b vb,那么现在产生的贡献是 y a + w a + v b y_a+w_a+v_b ya+wa+vb

考虑后续有一个箱子要抢夺传送点 b b b,添加一个代价为 − y a − w a -y_a-w_a yawa的传送点。

考虑后续有一个传送点要抢夺箱子 a a a,添加一个代价为 − v b − 2 y a -v_b-2y_a vb2ya的箱子。

变形4.分身

在任意条件组合基础上,添加“传送点最多可以激活 c i c_i ci次,每次激活代价为 w i w_i wi”或“第 i i i个位置叠着 d i d_i di个箱子”这两个条件, c i c_i ci d i d_i di都较大( 1 0 9 10^9 109级别)

在堆里直接放一个pair,记录一下这种箱子或传送点有多少个即可。什么?你说合并的时候产生的新箱子和新传送点数目可能会很多?呃,这个是可以证明不会产生太多的,好像是用什么匹配不会交叉证,但我不会,嘤嘤嘤。

接下来可以做一道题了,UOJ#455 雪灾与外卖

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
typedef pair<LL,LL> PR;
const LL inf=1e15;
const int N=100005;
int n,m;LL ans,sumc;
struct node{LL x,w,c;}t[N<<1];
bool cmp(node A,node B) {return A.x<B.x;}
priority_queue<PR,vector<PR>,greater<PR> > q1,q2;

void work() {
	q1.push((PR){inf,n});
	for(RI i=1;i<=n+m;++i) {
		if(t[i].w==-1) {
			PR kl=q1.top();LL kv=kl.first;q1.pop();
			ans+=kv+t[i].x,--kl.second;
			if(kl.second) q1.push(kl);
			q2.push((PR){-kv-2*t[i].x,1});
		}
		else {
			LL num=t[i].c,sumsum=0;
			while(num&&(!q2.empty())) {
				PR kl=q2.top();LL kv=kl.first;
				if(kv+t[i].w+t[i].x>=0) break;
				q2.pop();LL sum=min(kl.second,num);
				ans+=(kv+t[i].w+t[i].x)*sum,kl.second-=sum,num-=sum;
				if(kl.second) q2.push(kl);
				sumsum+=sum,q1.push((PR){-kv-2*t[i].x,sum});
			}
			if(sumsum) q2.push((PR){-t[i].x-t[i].w,sumsum});
			if(num) q1.push((PR){t[i].w-t[i].x,num});
		}
	}
}
int main()
{
	n=read(),m=read();
	for(RI i=1;i<=n;++i) t[i].x=read(),t[i].w=-1,t[i].c=0;
	for(RI i=1;i<=m;++i)
		t[i+n].x=read(),t[i+n].w=read(),t[i+n].c=read(),sumc+=t[i+n].c;
	if(sumc<(LL)n) {puts("-1");return 0;}
	sort(t+1,t+1+n+m,cmp),work();
	printf("%lld\n",ans);
	return 0;
}

变形5.分身必须匹配一个

若分身必须匹配一个,拆成两种,一种有一个,匹配后产生额外价值 − i n f -inf inf(求最小价值),一种有 c i − 1 c_i-1 ci1个,匹配后产生额外价值 0 0 0

变形6.上树

上树愉快!使用可并堆,自底向上合并,每一次合并将来自不同子树的箱子和传送点配一配。

复杂度还是不会证QAQ。

loj#6405 征服世界

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
typedef pair<LL,int> PR;
const int N=250005;
const LL inf=1e12;
int n,tot,SZ;LL ans;
int h[N],ne[N<<1],to[N<<1],X[N],Y[N],rtX[N],rtY[N];LL dis[N],w[N<<1];
struct node{int ls,rs,d;PR v;}tr[N*30];
//申请超大结构体要花很多时间,建议拆成单个数组。

int merge(int a,int b) {
	if(!a||!b) return a|b;
	if(tr[a].v>tr[b].v) swap(a,b);
	tr[a].rs=merge(tr[a].rs,b);
	if(tr[tr[a].rs].d>tr[tr[a].ls].d) swap(tr[a].ls,tr[a].rs);
	tr[a].d=tr[tr[a].rs].d+1;return a;
}
int newnode(LL val,int sum)
	{++SZ,tr[SZ].v=(PR){val,sum},tr[SZ].d=1;return SZ;}

void add(int x,int y,LL z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void mer(int x,int y,LL d) {
	while(rtX[x]&&rtY[y]) {
		LL v1=tr[rtX[x]].v.first,v2=tr[rtY[y]].v.first;
		if(v1+v2-2*d>=0) break;
		int sum=min(tr[rtX[x]].v.second,tr[rtY[y]].v.second);
		ans+=(v1+v2-2*d)*sum;
		tr[rtX[x]].v.second-=sum,tr[rtY[y]].v.second-=sum;
		if(!tr[rtX[x]].v.second) rtX[x]=merge(tr[rtX[x]].ls,tr[rtX[x]].rs);
		if(!tr[rtY[y]].v.second) rtY[y]=merge(tr[rtY[y]].ls,tr[rtY[y]].rs);
		rtX[x]=merge(rtX[x],newnode(-v2+2*d,sum));
		rtY[y]=merge(rtY[y],newnode(-v1+2*d,sum));
	}
}
void dfs(int x,int las) {
	if(X[x]) rtX[x]=newnode(dis[x],X[x]);
	if(Y[x]) rtY[x]=newnode(dis[x]-inf,Y[x]),ans+=1LL*Y[x]*inf;
	for(RI i=h[x];i;i=ne[i]) {
		int y=to[i];if(y==las) continue;
		dis[y]=dis[x]+w[i],dfs(y,x);
		mer(x,y,dis[x]),mer(y,x,dis[x]);
		rtX[x]=merge(rtX[x],rtX[y]),rtY[x]=merge(rtY[x],rtY[y]);
	}
}
int main()
{
	int x,y,z;
	n=read();
	for(RI i=1;i<n;++i)
		x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
	for(RI i=1;i<=n;++i) {
		X[i]=read(),Y[i]=read();
		int kl=min(X[i],Y[i]);
		X[i]-=kl,Y[i]-=kl;
	}
	dfs(1,0);
	printf("%lld\n",ans);
	return 0;
}
  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值