最普通的一类老鼠进洞问题

看了一个下午的WC2019的模拟费用流的课件,感觉什么都不会……


最普通的一类老鼠进洞问题:

(n)只老鼠,坐标分别为(x_i);有(m)个洞,有坐标(y_i),代价(w_i),容量(c_i)。老鼠(i)进洞(j)的代价为(|x_i-y_i|+w_i)。每只老鼠必须进洞。

板子题:https://uoj.ac/problem/455

显然有性质:老鼠和洞的匹配不能相交。(也就是说对于任意(x_i<x_j),一定有(p_ile p_j)(p)表示匹配对象)

考虑按照坐标从左往右加入老鼠和洞。对老鼠和洞分别维护堆(Q0)(Q1)。为了实现方便,提前加上代价为无穷的洞。

如果加入老鼠(x)

(Q1)中取出代价最少的,设代价为(v)

则答案加上(x+v)

因为老鼠可能会反悔,所以在(Q0)中加入(-(x+v)-x),表示匹配后面的洞的代价。

如果洞反悔,由于老鼠必须进洞,它应该要往后再找一个洞,此时匹配相交。

如果加入洞((y,w,c))

在容量被耗尽或者不优(即匹配老鼠代价不为负数)前,重复从(Q0)中取出代价最少的老鼠,设其代价为(v)

答案加上(w+y+v)

因为老鼠可能会反悔选更后面的,所以在(Q0)加入(-(w+y+v)+v)表示匹配后面的代价。

因为洞可能反悔匹配后面的老鼠,所以在(Q1)加入(-(w+y+v)+w-y)表示匹配后面的代价。

实际上两种操作在后面不可能都发生,否则匹配相交。并且也不需要考虑进行反悔操作之后又有老鼠或洞重新回来匹配。

退出循环,把剩下的容量个洞加入(Q1)

因为这里老鼠可能会反悔多次,一个个加不优。注意到此时加的代价一样,就可以记下同样代价的数量一起处理。

具体实现见代码:

using namespace std;
#include <bits/stdc++.h>
#define N 100005
#define ll long long
#define INF 2000000000
int n,m;
ll X[N];
ll Y[N],W[N],C[N];
ll ans;
struct info{ll v,cnt;};
bool operator<(info a,info b){return b.v<a.v;}
priority_queue<info> q0,q1;
void ins0(ll x){
	info t=q1.top();
	q1.pop();
	ans+=x+t.v;
	if (--t.cnt)
		q1.push(t);
	q0.push((info){-(x+t.v)-x,1});
}
void ins1(ll x,ll w,ll c){
	ll s=0;
	while (c && !q0.empty()){
		info t=q0.top();
		if (w+x+t.v>=0)
			break;
		q0.pop();
		ll mn=min(t.cnt,c);
		ans+=(w+x+t.v)*mn;
		q1.push((info){-(w+x+t.v)-x+w,mn});
		s+=mn;
		t.cnt-=mn;
		c-=mn;
		if (t.cnt)
			q0.push(t);
	}
	if (c) q1.push((info){-x+w,c});
	if (s) q0.push((info){-(w+x/*+t.v*/)/*+t.v*/,s});
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i) scanf("%lld",&X[i]);
	ll sum=0;
	for (int i=1;i<=m;++i) scanf("%lld%lld%lld",&Y[i],&W[i],&C[i]),sum+=C[i];
	if (sum<n){
		printf("-1
");
		return 0;
	}
	q1.push((info){INF,INF});
	int i=1,j=1;
	while (i<=n || j<=m)
		if (i<=n && (j>m || X[i]<Y[j]))
			ins0(X[i]),i++;
		else
			ins1(Y[j],W[j],C[j]),j++;
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/jz-597/p/14326929.html