再探模拟费用流一类问题

参考博客:

https://www.cnblogs.com/Miracevin/p/11151822.html

https://blog.csdn.net/litble/article/details/88410435

https://www.cnblogs.com/uid001/p/11348566.html

老鼠进洞模型1:

一条数轴上,有n只老鼠,m个洞,一个洞最多容纳一只老鼠。

老鼠只能往左走,走到一个洞的代价为坐标差绝对值,求所有老鼠进洞的最小代价。

从左往右扫,每只老鼠进能进的最近的就好了。

模型2:

现在老鼠不一定要全部进洞。

选一个老鼠进洞有额外的代价\(w1[i]\),进第\(i\)个洞有额外的代价\(w2[i]\in R\),不进就没有代价。

求最大的代价和。

费用流模型显然,对于这类问题,直接模拟费用流会有些吃力。

考虑更改一下费用流的顺序,现在从左往右求解,每次经过一个老鼠或者洞,就更新当前的残量网络,使其变为更优的。

经过一个老鼠\(x[i]\)时,老鼠可以在前面选一个洞进,当然代价和要\(>0\)才选,选了这个洞后,后面的老鼠代替当前这只老鼠选这个洞可能更优,相当于添加代价\(-x[i]-w1[i]\)的一个洞。

经过一个洞\(y[i]\)时,就是添加一个代价为\(w2[i]-y[i]\)的洞。

用堆动态维护当前的洞的代价,每次经过老鼠时从堆中取出最大的元素即可。

题目:

http://www.lydsy.com/JudgeOnline/problem.php?id=4977

代码:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;
 
const int N = 2e5 + 5;
 
int n, m;
 
struct P {
    int ty, x, y;   
} b[N]; int b0;
 
int cmpb(P a, P b) {
    if(a.x == b.x) return a.ty < b.ty;
    return a.x < b.x;
}
 
priority_queue<int> q;
 
ll ans;
 
int main() {
    scanf("%d %d", &n, &m);
    fo(i, 1, n) {
        b0 ++;
        scanf("%d", &b[b0].x);
        b[b0].ty = 0;
    }
    fo(i, 1, m) {
        b0 ++;
        scanf("%d %d", &b[b0].x, &b[b0].y);
        b[b0].ty = 1;
    }
    sort(b + 1, b + b0 + 1, cmpb);
    fo(i, 1, b0) {
        if(!b[i].ty) {
            if(q.size() && b[i].x + q.top() > 0) {
                ans += b[i].x + q.top(); q.pop();
                q.push(-b[i].x);
            }
        } else {
            q.push(b[i].y - b[i].x);
        }
    }
    pp("%lld\n", ans);
}

模型3:

模型2基础上,老鼠必须全部进洞。

把每个老鼠的额外代价\(+=inf\),最后答案\(-=inf*n\)即可。

模型4:

模型2基础上,老鼠可以往左也可以往右走。

费用流模型还是很显然的,还是从左往右考虑模拟费用流。

假设现在已经得到了前面的老鼠和洞的残量网络(即匹配关系),现在在后面加入一个老鼠或洞。

1.加入老鼠:

老鼠有三种选择:

a.啥也不选,让后面的洞来选它。

b.选择前面的一个洞

c.抢前面老鼠的一个洞

模型2中讲过,bc其实可以算作一种。

根据费用流特点,它会选三种中最优的一种,那么选即可。

2.加入洞:

洞有三种选择:

a.啥也不选,让后面的老鼠来选它。

b.选择前面一只老鼠

c.抢前面一个洞选的老鼠

bc其实也是一种,它还是会选三种最优的一种。

那么我们就得到了一个做法,用两个堆动态地维护可以选的老鼠、洞的代价。

每次取最优的选择后,在堆上进行对应的修改即可。

模型5:

模型4基础上,每个洞有一个容量上限,这个洞每多容纳一个老鼠,代价+w[i]。

http://uoj.ac/problem/455

考虑在堆中的元素改成一个pair,第二维的含义即这个老鼠(洞)还能容纳多少个。

考虑每次老鼠的加入会消耗至多一个洞,而每次洞的加入虽然一次可能会用掉多只老鼠,但是把用掉的老鼠都缩成一堆,那么复杂度就没有问题了。

Code:

http://uoj.ac/submission/390069

模型6:

模型5基础上,每个洞至少选一个老鼠。

把一个容量为\(c[i]\)洞的拆成\(1\)\(c[i]-1\)的洞,其中\(1\)的洞利用模型3的方法即可。

模型7:

上树,复杂度不会证了。

https://loj.ac/problem/6405

至此,老鼠进洞模型差不多搞完了。


其它题目总结:

模拟费用流问题做的肯定是有费用流模型的题目,有些时候,把费用流模型建出来有利于模拟的实现,但有些时候,直接思考贪心也是一种不错的方法。

1.D. Buy Low Sell High

这个题费用流可以建:
对于第\(i\)个点,拆点为\(i\)\(i'\)

\(i->i',r=1,cost=0\)

\(S->i,r=1,cost=-w[i]\)

\(i'->T,r=1,cost=w[i]\)

\(i'->i对应的数轴上的点,r=1,cost=0\)

\(i对应的数轴上的点->i,r=1,cost=0\)

\(数轴上,x->x+1,r=1,cost=0\)

最大流最大费用即是答案。

因为是数轴上的问题,考虑从左往右增量模拟。

假设现在加入i,那么可以在i卖出在前面j买入,

当利益\(>0\)形成一次交易后,后面的天可以:

1.把i扔出去,它来卖

2.以i作为买入的地方

先有1再有2,代价都是\(-w[i]\),所以看做有两个\(-w[i]\)的买入地方,扔进堆里。

如果形不成交易,那么只有2,扔一个\(-w[i]\)到堆里。

不难发现这题不建费用流模型也可以思考。

Code:

http://codeforces.com/contest/865/submission/74783502

2.I. Olympiad in Programming and Sports

这个费用流模型就很显然唉。

建两个点\(X,Y\),分别表示选编程还是运动。

\(S->X,r=p,cost=0\)

\(S->Y,r=s,cost=0\)

\(X->i,r=1,cost=a[i]\)

\(y->i,r=1,cost=b[i]\)

\(i->T,r=1,cost=0\)

最大流最大费用即答案,可以通过这题的数据。

这题就考虑直接整个模拟了。

模拟费用流,发现就是接下来的步骤做\(p+s\)步:
从以下两个中选最优的:
1.取最大的没被取的\(a[i]\)放进A,当p满时,加上额外的最小的代价为从A中挪一个到B。

2.1的对称操作。

取完后在堆中做对应的操作,一共要维护4个堆。

Code:

http://codeforces.com/contest/730/submission/74831775

3.「NOI2019」序列

回过头来看这个题还挺简单的(赛场时怎么不会.jpg)

这个题的费用流模型没那么简单。

相同的至少\(l\)个,相当于不同的至多\(k-l\)个。

首先每个点拆点为\(i\)\(i'\)

\(S->i,r=1,cost=a[i]\)

\(i->i',r=1,cost=0\)

\(i'->T,r=1,cost=b[i]\)

这样完成了相同位置的构图,额外建两个点\(X,X'\)用来中转不同的位置选择。

\(X->X',r=k-l,cost=0\)

\(i->X,r=1,cost=0\)

\(X'->i',r=1,cost=0\)

这个图的流量为\(k\)的最大费用流即答案。

还是考虑直接整个图模拟。

模拟费用流,发现是一下过程模拟\(k\)次:

以下五种情况选最大:

1.如果\(X->X'\)还有流量,选\(max(a[i])+max(b[j])(a[i]、b[j]自己和对应位置都没有选过的)\)

2.选个\(max(a[i]+b[i])(a[i]、b[i]没有被选过)\)

\(c[i]\)为已经选过的\(b[i]\)对应的\(a[i]\)\(d[i]\)对称定义。

3.\(max(c[i])+max(b[j])\)

\(4.max(a[i])+max(d[j])\)

\(5.max(c[i])+max(d[j])\),这个操作后\(X->X'\)会退流1.

维护五个堆,每次进行对应的操作即可。

卡常:multiset TLE了,请用priority_queue+懒惰删除。

Code:

https://loj.ac/submission/777519


可撤销贪心:

模拟费用流本质上是一个可撤销贪心,很多可撤销贪心不一定有简单的费用流模型,但是我们可以用模拟费用流那套在残量网络上修改的思想去理解可撤销贪心。

1.http://www.lydsy.com/JudgeOnline/problem.php?id=2151

2.https://www.luogu.com.cn/problem/P4053

3.http://acm.hdu.edu.cn/showproblem.php?pid=6698

这三题是比较清晰的,网上题解大把,这里不写了。

posted @ 2020-03-31 17:10  Cold_Chair  阅读(920)  评论(2编辑  收藏  举报