最普通的一类老鼠进洞问题
看了一个下午的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_i\le 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\n");
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\n",ans);
return 0;
}