[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);
}
相關文章
相關標籤/搜索