【堆模拟费用流增广】UOJ455 [UER #8] 雪灾与外卖

【题目】
原题地址
一条直线上有 n n n个送餐员和 m m m个餐厅。每个送餐员都要去餐厅取菜,花费为距离+菜的价值。每个餐厅有提供菜数量限制,求最小花费。 n , m ≤ 1 0 5 n,m\leq 10^5 n,m105,其他数字 ≤ 1 0 9 \leq 10^9 109

【解题思路】
题解看这
设送餐员为 a a a,餐厅为 b b b,坐标为 x x x
首先我们可以观察到一个十分 naive \text{naive} naive的性质: ∀ 1 ≤ a i < a j ≤ n , 1 ≤ b k < b l ≤ m , ∣ x a i − x b k ∣ + ∣ x a j − x b l ∣ ≤ ∣ x a j − x b k ∣ + ∣ x a i − x b l ∣ \forall 1\leq a_i<a_j\leq n,1\leq b_k<b_l\leq m,|x_{a_i}-x_{b_k}|+|x_{a_j}-x_{b_l}|\leq |x_{a_j}-x_{b_k}|+|x_{a_i}-x_{b_l}| 1ai<ajn,1bk<blm,xaixbk+xajxblxajxbk+xaixbl
于是我们按顺序 DP \text{DP} DP,状态为前 i i i个送餐员匹配到前 j j j个餐厅的最小花费,转移时枚举第 j j j个餐厅匹配多少个送餐员,用单调队列优化可以做到 O ( n m ) O(nm) O(nm)

考虑一个更显然的问题,这个就是一个二分图匹配问题,我们建图跑费用流就可以了。
然而这样做并不优秀,于是我们考虑套路,用堆或线段树来模拟费用流的增广,这里显然是用堆。

我们考虑把餐厅和送餐员放在一起按照坐标排序,从前往后考虑,当考虑到一名送餐员 i i i时,我们先让它与前面的一个空的餐厅 j j j匹配。费用为 x i − x j x_i-x_j xixj,那么我们就是要找一个最小的 − y j -y_j yj。(如果没有空的餐厅我们可以视为在 − ∞ -\infty 处有无穷个餐馆)。

匹配完之后,我们可能进行调整,把这名送餐员改为匹配后面的餐厅。那么我们就可以撤销这次操作,然后把这名送餐员当做没有使用过,那么往堆中丢入 − ( x i − x j ) − x i -(x_i-x_j)-x_i (xixj)xi,即减去之前这组匹配的贡献,然后寻找新匹配。 扫到餐厅时也类似,先可以匹配一名送餐员,然后可以撤销这组匹配,寻找新匹配。

对所有餐厅和送餐员分别建一个堆来维护这个操作。注意我们既可以对送餐员进行反悔,也可以对餐厅进行反悔,即每新增一组匹配,我们往两个堆中分别加入反悔操作。

设每个餐厅能提供 c i c_i ci的菜,复杂度是O ( ( n + ∑ c i ) log ⁡ ( n + ∑ c i ) ) ((n+\sum c_i)\log (n+\sum c_i)) ((n+ci)log(n+ci))
这个就是一个纯模拟费用流了。

考虑这个过程中我们维护了两个堆,一个维护待匹配的送餐员,一个维护待匹配的餐厅。之前复杂度不对的原因是送餐员堆的复杂度没有保证,因为一个餐厅匹配了一名送餐员之后,这名送餐员可能反悔,所以这名送餐员又会重新丢入送餐员堆中。

但注意到对于送餐员 x , y x,y x,y和餐厅 a , b a,b a,b来说,若 x < y < a < b x<y<a<b x<y<a<b,那么当送餐员 x x x y y y在匹配了餐馆 a a a时,丢到堆里的元素都是 − x a − w a -x_a-w_a xawa,也就是说每次餐馆匹配了若干送餐员后,往送餐员堆里面丢的东西都是等权的,因此只需要丢一次就行了。

复杂度就是 O ( n log ⁡ n ) O(n\log n) O(nlogn)的了。

【参考代码】

#include
using namespace std;

typedef long long ll;
const int N=2e5+10,INF=0x3f3f3f3f;
int n,m,cnt;
ll ans,tot;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct node
{
	int x,w,c;
	node(int _x=0,int _w=0,int _c=0):x(_x),w(_w),c(_c){}
	bool operator <(const node&a)const{return x<a.x;}
}a[N];

struct heap
{
	ll v;int t;
	heap(ll _v=0,int _t=0):v(_v),t(_t){}
	bool operator <(const heap&a)const{return v==a.v?t<a.t:v>a.v;}
};
priority_queue<heap>q1,q2;

int main()
{
#ifndef ONLINE_JUDGE
	freopen("UOJ455.in","r",stdin);
	freopen("UOJ455.out","w",stdout);
#endif
	n=read();m=read();cnt=n;
	for(int i=1;i<=n;++i) a[i].x=read();
	for(int i=1;i<=m;++i)
	{
		int x=read(),w=read(),c=min(read(),n);tot+=c;
		if(c) a[++cnt]=node(x,w,c);
	}
	if(tot<n){puts("-1");return 0;}
	a[++cnt]=node(-INF,INF,N);
	sort(a+1,a+cnt+1);
	for(int i=1;i<=cnt;++i)
	{
		int x=a[i].x,w=a[i].w,c=a[i].c;
		if(c)//restaurant
		{
			int s=0;
			while(c && !q1.empty())
			{
				ll v=q1.top().v;
				if(v+w+x>=0) break;
				int t=q1.top().t,res=min(c,t);q1.pop();
				ans+=(ll)res*(v+w+x);
				q2.push(heap(-v-2*x,res));
				s+=res;c-=res;t-=res;
				if(t) q1.push(heap(v,t));
			}
			if(s)q1.push(heap(-w-x,s));
			if(c)q2.push(heap(w-x,c));
		}
		else
		{
			ll v=q2.top().v;
			int t=q2.top().t-1;q2.pop();
			ans+=(ll)v+x;
			q1.push(heap(-v-2*x,1));
			if(t)q2.push(heap(v,t));
		}
	}
	printf("%lld\n",ans);

	return 0;
}

你可能感兴趣的:(基础数据结构-堆)