题解-UOJ 455雪灾与外卖

时间:2021-07-09 12:53:20

Problem

\(\mathrm{UOJ~455}\)

题意概要:一根数轴上有 \(n\) 只老鼠与 \(m\) 个洞,每个洞有费用与容量限制,要求每只老鼠要进一个洞且每个洞的老鼠不超过自身的容量限制,定义一种方案的费用为所有老鼠移动距离之和加上所有老鼠进的洞费用之和(若一个洞进了 \(k\) 只老鼠,则费用需要计算 \(k\) 次)

\(n,m\leq 10^5\)

Solution

冬令营时掉线了,只记得这题被大家把好评刷上去了

这题是所谓模拟费用流问题。我理解的模拟费用流其实等价于处理“反悔”也即反向弧,因为费用流问题是使用 最短路(贪心) 加 反向弧(反悔) 解决的,所以利用该种思想,在平时题目中,若贪心有瑕疵,可以考虑维护后悔操作。相似的题有bzoj4977跳伞逃生

将所有老鼠和洞按照坐标排序先

bzoj4977相当于这题中老鼠只能往左走,即只能往前匹配,这样可以从左往右维护一个堆解决贪心。

而这题中老鼠和洞都可能往右匹配,但是依照上述“贪心+后悔”的思想,考虑先让老鼠和洞往左匹配。

维护老鼠和洞两个堆,分别设为 \(A,B\);明确老鼠进洞的代价是两个坐标中靠右的减去靠左的。

下面开始讨论:

若当前为老鼠,且其坐标为 \(x\),若从 \(B\) 中取得的最优解为 \(v\),则对答案贡献 \(x+v\)。这样的话只考虑了向左的匹配,但实际有可能向右匹配(反悔),考虑将这次匹配的贡献消除,且将此点设定为左端,则需向 \(A\) 中加入 \(-(v+x)-x\)(前者将当前贡献消除,后者假定当前为左端点需减去)

若当前为餐厅,且其坐标为 \(y\),费用为 \(w\),若从 \(A\) 中取得的最优解为 \(v\),对答案贡献 \(w+y+v\),与上头类似,为了维护反悔操作需在 \(B\) 中加入 \(-(w+y+v)-y\)

打完后发现样例中有俩过不去,问了大神们才知道我少考虑了一种情况:若当前顺序为 鼠、洞、洞,我的方法一旦匹配了第一个洞和鼠后,不能考虑鼠反悔去匹配第二个洞的情况

那么就再加入一种反悔操作:往 \(A\) 中加入 \(-w-y\) 即可。具体地说,假设鼠价值 \(v\),两个洞的费用与坐标分别为 \(w_1,w_2,y_1,y_2\),那从第一种情况反悔到第二种情况的贡献为 \((w_2+y_2+v)-(w_1+y_1+v)=w_2+y_2-w_1-y_1\),由于 \(w_2+y_2\) 会在后面统计到,所以只需往堆中加入 \(-w_1-y_1\) 即可。至于为什么不需要对 洞、鼠、鼠 的情况做反悔,因为所有老鼠都是一样的,不可能存在舍近求远的情况

还有一点,由于所有老鼠都要求进洞,若最左侧的老鼠在最左侧的洞的左边,则这只老鼠有可能不会进洞,所以可以在最最最左侧加入一个费用无穷大的洞,这样左侧的老鼠匹配它后立马就会被替换掉,进而所有老鼠都会被匹配(在有解的情况下)

至于如何考虑每个洞的容量限制,可以将每个洞作为一个整体放进堆里,记录下剩余使用次数,作为反悔可以每次分裂一个出来

虽然思想比较简单,但是由于用到多次反悔操作(甚至有反悔后的反悔),所以代码难以调试,建议静态差错,最好一遍过

Code

代码中 \(P\) 表示老鼠,\(R\) 表示洞

//uoj-455
#include <bits/stdc++.h>
using namespace std;
typedef long long ll; typedef pair <ll,int> pr;
#define mp make_pair template <typename _tp> inline void read(_tp&x){
char c11=getchar(),ob=0;x=0;
while(c11!='-'&&!isdigit(c11))c11=getchar();if(c11=='-')ob=1,c11=getchar();
while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;
} const int N = 401000;
const ll Inf = 2e17;
int x[N],n,m; struct node{
int y,w,c;
inline void in(){read(y),read(w),read(c);}
friend inline bool operator < (const node&A,const node&B) {return A.y < B.y;}
}p[N]; struct Heap{
pr h[N]; int tp;
inline bool empty() {return !tp;}
inline pr top() {return h[1];}
inline void push(pr x) {h[++tp] = x; push_heap(h+1,h+tp+1,greater <pr> ());}
inline void pop() {pop_heap(h+1,h+tp+1,greater <pr> ()); --tp;}
}R,P; int main(){
read(n),read(m);
ll tot = 0;
for(int i=1;i<=n;++i) read(x[i]); sort(x+1,x+n+1);
for(int i=1;i<=m;++i) p[i].in(), tot += p[i].c; sort(p+1,p+m+1); if(tot < n) {puts("-1"); return 0;} R.push(mp(Inf,2e8));
x[n+1] = 2e9; ll Ans = 0ll;
for(int i=1,j=1;i<=n or j<=m;) {
while(p[j].y <= x[i] and j <= m){
int usc = 0;
while(!P.empty() and p[j].c){
pr nw = P.top();
ll val = p[j].w + p[j].y + nw.first;
if(val > 0) break; P.pop();
int ct = min(nw.second, p[j].c);
Ans += (ll)ct * val;
R.push(mp(-nw.first - 2*p[j].y, ct));
p[j].c -= ct, usc += ct, nw.second -= ct;
if(nw.second) P.push(nw);
}
if(usc) P.push(mp(-p[j].w - p[j].y, usc));
if(p[j].c) R.push(mp(p[j].w - p[j].y, p[j].c));
++j;
}
if(i > n) continue;
pr nw = R.top(); R.pop();
Ans += nw.first + x[i];
P.push(mp(-(nw.first + x[i]) - x[i], 1));
--nw.second; if(nw.second) R.push(nw);
++i;
}
printf("%lld\n",Ans);
return 0;
}