[UOJ455][UER #8]雪灾与外卖——堆+模拟费用流

题目连接:

[UOJ455]雪灾与外卖ios

题目描述:有$n$个送餐员(坐标为$x_{i}$)及$m$个餐厅(坐标为$y_{i}$,权值为$w_{i}$),每一个送餐员须要前往一个餐厅,每一个餐厅只能容纳$c_{i}$个送餐员,一个送餐员去一个餐厅的代价为$|x_{i}-y_{j}|+w_{j}$,求最小代价。

 首先这个题能够暴力建图跑费用流,具体作法就不说了。如今咱们考虑模拟费用流的过程,也就是模拟贪心及匹配中反悔的过程。spa

咱们对送餐员和餐厅分别开一个小根堆而后从左往右决策每一个坐标位置的人或餐厅的选择:code

对于送餐员,先强制让他与左面的餐厅匹配(若是没有则看做和无限远处匹配),为了使代价最小,咱们选择左面$w-y$最小的餐厅与他匹配,由于他还可能与右边的餐厅匹配,因此咱们往送餐员的堆中加入一个当前送餐员的反悔操做,权值为$-(x-y+w)-x$(由于这个反悔操做只会匹配右边的餐厅,因此送餐员以后的权值为$-x$),这样若是以后选择这个反悔操做,就会将以前选择的代价抵消掉,并让这个送餐员产生新的代价。这也就是说送餐员的堆中存的都是反悔的送餐员。blog

对于餐厅,只要当前餐厅的权值$w+y$与送餐员的堆顶的权值之和小于$0$就说明这个堆顶的送餐员匹配当前餐厅比以前的选择更优,那么咱们就让他匹配当前餐厅。这时候有两种状况:一、餐厅反悔,它要匹配它右边的送餐员,那么咱们在餐厅的堆中加入权值为$-(v+w+y)+w-y$的反悔操做(其中$v$表示送餐员堆顶的权值,由于这个反悔操做只会匹配右边的送餐员,因此餐厅的权值为$w-y$)。二、送餐员反悔,他要匹配更右边的餐厅,这时就要在送餐员的堆中加入权值为$-w-y$的反悔操做来使下一次选到这个操做时抵消掉此次匹配的代价。get

总的来讲这道题就是在全部正常匹配和反悔操做中贪心寻找最优解来进行匹配。string

最后说一下时间复杂度:由于对于送餐员向左匹配时只会反悔一次因此送餐员的反悔操做进堆次数是线性的。对于餐厅的操做,由于一个餐厅匹配左面的送餐员时送餐员的反悔操做权值都是$-y-w$,因此只须要记录一下匹配数量,统一入堆便可。每一个反悔操做在被匹配后都会删除,而除了送餐员向左匹配的反悔操做以外,只会在枚举到每一个餐厅时将一个权值入堆,因此总共能被匹配的送餐员反悔操做的数量是线性的。it

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
struct miku
{
	ll sum;
	ll num;
	miku(){}
	miku(ll SUM,ll NUM){sum=SUM,num=NUM;}
};
bool operator <(miku a,miku b){return a.sum>b.sum;}
priority_queue<miku>A,B;
ll x[100010];
ll y[100010];
ll w[100010];
ll c[100010];
int n,m;
ll ans;
void ins1(ll x)
{
	ll v=B.top().sum;
	int c=B.top().num;
	B.pop();
	ans+=x+v;
	A.push(miku(-(x+v)-x,1));
	if(c>1)
	{
		B.push(miku(v,c-1));
	}
}
void ins2(ll y,ll w,int c)
{
	int k=0;
	while(!A.empty()&&k<c&&A.top().sum+w+y<0)
	{
		ll v=A.top().sum;
		int s=A.top().num;
		A.pop();
		int g=min(s,c-k);
		s-=g,k+=g,ans+=1ll*g*(v+w+y);
		if(s)
		{
			A.push(miku(v,s));
		}
		B.push(miku(-(v+w+y)+w-y,g));
	}
	if(k)
	{
		A.push(miku(-y-w,k));
	}
	if(c-k)
	{
		B.push(miku(w-y,c-k));
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	ll tot=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&x[i]);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&y[i],&w[i],&c[i]);
		tot+=c[i];
	}
	if(tot<n)
	{
		printf("-1");
		return 0;
	}
	y[0]=-1ll<<60,c[0]=1<<30;
	int i=1,j=0;
	while(i<=n&&j<=m)
	{
		if(x[i]<=y[j])
		{
			ins1(x[i]);
			i++;
		}
		else
		{
			ins2(y[j],w[j],c[j]);
			j++;
		}
	}
	while(i<=n)
	{
		ins1(x[i]);
		i++;
	}
	while(j<=m)
	{
		ins2(y[j],w[j],c[j]);
		j++;
	}
	printf("%lld",ans);
}
相关文章
相关标签/搜索