看了一个下午的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_ile 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
");
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
",ans);
return 0;
}