【UOJ455】【UER #8】 雪灾与外卖(模拟费用流)
大致题意: 给定一条数轴,有\(n\)个位置各有一只兔子,还有\(m\)个位置各有\(c_i\)个洞,每个地方的洞有一个相同的权值\(v_i\)。每只兔子必须走到一个洞中,每个洞最多进一只兔子。求最小化所有兔子走的总距离以及所进洞的权值总和。
模拟费用流
考虑所有\(c_i=1\)的情况,就是一个模拟费用流的常规模型。
对于各种可能出现的情形分别考虑:(注意,以下的代价均指在堆中的值)
- 考虑一只兔子\(i\)如果选择了一个代价为\(V\)的洞,对答案贡献就是\(x_i+V\)。
- 如果之后一个洞\(j\)替换了这个洞,答案应该加上\(y_j+v_j-2x_i-V\)(注意,兔子坐标对答案的贡献从正变成了负,故要减去\(2x_i\))。也就是说,要加入一只代价为\(-2x_i-V\)的兔子。
- 这种情况下别的兔子不能替换这只兔子,否则这只兔子就没有洞了,违背了每只兔子一定要进洞的原则。
- 考虑一个洞\(j\)如果选择了一只代价为\(W\)的兔子,对答案贡献就是\(y_j+v_j+W\)。
- 如果之后一只兔子\(i\)替换了这只兔子,答案应该加上\(x_i-2y_j-W\)。也就是说,要加入一个代价为\(-2y_j-W\)的洞。(注意,这里不会因为原兔子被替换而违背每只兔子一定要进洞的原则,因为它在洞\(j\)选择它之前就有一个匹配的洞,洞\(j\)被抢走后它又会找回原先的洞,一切都回到洞\(j\)选择它之前的样子)
- 如果之后一个洞\(k\)替换了这个洞,答案应该加上\(y_k+v_k-y_j-v_j\)。也就是说,要加入一只代价为\(-y_j-v_j\)的兔子。
看起来很绕,不过其实只要知道,兔子已经不是单纯的兔子,洞已经不是单纯的洞,但兔子和洞之间依旧存在着匹配关系,就仍旧可以用堆来解决这个问题。
无论怎样,之所以问题会这么复杂,只是为了高效地完成退流操作而已。
分身
分身的情况其实非常简单啦。
我们直接把一个位置上的洞用pair
绑起来,然后同样按照上面的做法去做即可。
复杂度是可以证明的,反正我不会就是了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
#define INF 1e12
using namespace std;
int n,m;struct Data
{
int x,v,c;I bool operator < (Con Data& o) Con {return x<o.x;}//存储信息
}s[2*N+5];
struct Pair
{
LL v;int c;I Pair(Con LL& a=0,CI b=1):v(a),c(b){}//把同样的洞绑成pair
I bool operator < (Con Pair& o) Con {return v>o.v;}
};priority_queue<Pair> A,B;//A存储洞,B存储兔子
int main()
{
RI i,k;LL g=0;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",&s[i].x),s[i].v=-1;
for(i=1;i<=m;++i) scanf("%d%d%d",&s[n+i].x,&s[n+i].v,&s[n+i].c),g+=s[n+i].c;if(g<n) return puts("-1"),0;//判无解
LL ans=0;Pair t;for(sort(s+1,s+n+m+1),A.push(Pair(INF,n)),i=1;i<=n+m;++i) if(!~s[i].v)//初始加入n个INF保证每只兔子一定要进洞
{
t=A.top(),A.pop(),ans+=s[i].x+t.v,--t.c&&(A.push(t),0),B.push(Pair(-2LL*s[i].x-t.v,1));//兔子只有1只,处理较为简单
}
else
{
g=0;W(!B.empty()&&s[i].c)//只要还能配对就不断搞
{
if(t=B.top(),s[i].x+s[i].v+t.v>=0) break;B.pop();//洞无需用完,当会使答案变劣就结束
k=min(s[i].c,t.c),s[i].c-=k,t.c-=k,g+=k,ans+=(s[i].x+s[i].v+t.v)*k,//配对k次
t.c&&(B.push(t),0),A.push(Pair(-2LL*s[i].x-t.v,k));//入堆
}
g&&(B.push(Pair(-s[i].x-s[i].v,g)),0),s[i].c&&(A.push(Pair(-s[i].x+s[i].v,s[i].c)),0);//把同样的兔子/洞一起加入堆中
}
return printf("%lld\n",ans),0;//输出答案
}
待到再迷茫时回头望,所有脚印会发出光芒