网络流水题五杀——洛谷 P3701、P2472、P1129、P2053、P2805

来源:互联网 发布:特价机票全额退款 知乎 编辑:程序博客网 时间:2024/06/18 12:24

前言

前段时间,趁着NOIP爆炸后,狂补文化课的空隙,一口气刷了好多网络流的shui ti……


[1星难度] 洛谷 P3701「伪模板」主席树 (二分图多重匹配)

题目传送门


题解

很明显,有两个人,所以是二分图,两两对决可以看作匹配,每个人可以和多个人对决,因此是二分图的多重匹配。我们将s连向byx的人,容量为每个人的寿命。然后手气君的人连向t,容量为寿命。这里提前算好长者续命后的寿命。然后从左向右,当A能打过B时,连一条边,容量为1。最后跑一遍最大流,然后由于只能打m场,要和m取个min。

坑点:两个字符串直接比较是否相等是不行的,我们拿第一个字符出来比才是珂学的。


代码

#include <cstdio>#include <cstdlib>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>#define maxn 100010#define maxm 1000010#define INF 0x7FFFFFFFusing namespace std;int n, m, s, t, cur = -1, level[maxn], q[maxn], life1[maxn], life2[maxn];char byx[maxn][5], sqj[maxn][5];struct List{    int obj, cap;    List *next, *rev;}Edg[maxm], *head[maxn], *iter[maxn];void Addedge(int a, int b, int c){    Edg[++cur].next = head[a];    Edg[cur].obj = b;    Edg[cur].cap = c;    Edg[cur].rev = Edg+(cur^1);    head[a] = Edg+cur;}bool bfs(){    int low = 0, high = 0;    q[0] = s;    for(int i = s; i <= t; i++)  level[i] = -1;    level[s] = 0;    while(low <= high){        int now = q[low++];        for(List *p = head[now]; p; p = p->next){            int v = p->obj, c = p->cap;            if(c && level[v] == -1){                level[v] = level[now] + 1;                q[++high] = v;            }        }    }    return level[t] != -1;}int Dinic(int now, int f){    if(now == t || !f)  return f;    int ret = 0;    for(List *&p = iter[now]; p; p = p->next){        int v = p->obj, c = p->cap;        if(c && level[v] > level[now]){            int d = Dinic(v, min(f, c));            f -= d;            p->cap -= d;            ret += d;            p->rev->cap += d;            if(!f)  break;        }    }    return ret;}int MaxFlow(){    int flow = 0;    while(bfs()){        for(int i = s; i <= t; i++)  iter[i] = head[i];        flow += Dinic(s, INF);    }    return flow;}int main(){    scanf("%d%d", &n, &m);    int cnt1 = 0, cnt2 = 0;    for(int i = 1; i <= n; i++){          scanf("%s", byx[i]);        if(byx[i][0] == 'Y')  cnt1 ++;    }    for(int i = 1; i <= n; i++){          scanf("%s", sqj[i]);        if(sqj[i][0] == 'Y')  cnt2 ++;    }    for(int i = 1; i <= n; i++){          scanf("%d", &life1[i]);        if(byx[i][0] == 'J')  life1[i] += cnt1;    }    for(int i = 1; i <= n; i++){          scanf("%d", &life2[i]);        if(sqj[i][0] == 'J')  life2[i] += cnt2;    }    s = 1;  t = s + n + n + 1;    for(int i = s; i <= t; i++)  head[i] = NULL;    for(int i = 1; i <= n; i++){        Addedge(s, s+i, life1[i]);        Addedge(s+i, s, 0);    }    for(int i = 1; i <= n; i++){        Addedge(s+n+i, t, life2[i]);        Addedge(t, s+n+i, 0);    }    for(int i = 1; i <= n; i++)        for(int j = 1; j <= n; j++){            if(byx[i][0] == 'W' && (sqj[j][0] == 'Y' || sqj[j][0] == 'E')){                Addedge(s+i, s+n+j, 1);                Addedge(s+n+j, s+i, 0);            }            if(byx[i][0] == 'J' && (sqj[j][0] == 'W' || sqj[j][0] == 'H')){                Addedge(s+i, s+n+j, 1);                Addedge(s+n+j, s+i, 0);            }            if(byx[i][0] == 'E' && (sqj[j][0] == 'Y' || sqj[j][0] == 'J')){                Addedge(s+i, s+n+j, 1);                Addedge(s+n+j, s+i, 0);            }            if(byx[i][0] == 'Y' && (sqj[j][0] == 'H' || sqj[j][0] == 'J')){                Addedge(s+i, s+n+j, 1);                Addedge(s+n+j, s+i, 0);            }            if(byx[i][0] == 'H' && (sqj[j][0] == 'E' || sqj[j][0] == 'W')){                Addedge(s+i, s+n+j, 1);                Addedge(s+n+j, s+i, 0);            }        }    printf("%d\n", min(MaxFlow(), m));    return 0;}

[1星难度] 洛谷 P2472 [SCOI2007]蜥蜴 (最大流)

题目传送门


题解

无法逃离的最小,不如求可以逃离的最大。我们由s向每个有蜥蜴的格子连边,容量为1,然后每个格子向可以到达的格子连边,容量为INF,但是每个格子有容量限制,于是我们拆点,格子分为入与出,格子间又出向入连,容量依旧INF,每个格子内部入向出连,容量为限制。然后如果一个格子能跳出地图的话,就向t连边,容量为INF。然后跑一遍最大流就是最多能跑掉的蜥蜴了。用总数减之就行了。好简单啊。构图一堆细节,容易敲错。


代码

#include <iostream>#include <algorithm>#include <cmath>#include <cstring>#include <cstdio>#include <cstdlib>#define maxn 100010#define maxm 1000010#define N 100#define INF 0x7FFFFFFFusing namespace std;int R, C, D, s, t, Sum;int cur = -1, level[maxn], q[maxn];char m1[N][N], m2[N][N];struct List{    int obj, cap;    List *next, *rev;}Edg[maxm], *head[maxn], *iter[maxn];void Addedge(int a, int b, int c){    Edg[++cur].next = head[a];    Edg[cur].obj = b;    Edg[cur].cap = c;    Edg[cur].rev = Edg+(cur^1);    head[a] = Edg+cur;}bool bfs(){    int low = 0, high = 0;    q[0] = s;    for(int i = s; i <= t; i++)  level[i] = -1;    level[s] = 0;    while(low <= high){        int now = q[low++];        for(List *p = head[now]; p; p = p->next){            int v = p->obj, c = p->cap;            if(!c || level[v] != -1)  continue;            level[v] = level[now] + 1;            q[++high] = v;        }    }    return level[t] != -1;}int Dinic(int now, int f){    if(now == t || !f)  return f;    int ret = 0;    for(List *&p = iter[now]; p; p = p->next){        int v = p->obj, c = p->cap;        if(c && level[v] > level[now]){            int d = Dinic(v, min(f, c));            f -= d;            p->cap -= d;            p->rev->cap += d;            ret += d;            if(!f)  break;        }    }    return ret;}int MaxFlow(){    int flow = 0;    while(bfs()){        for(int i = s; i <= t; i++)  iter[i] = head[i];        flow += Dinic(s, INF);    }    return flow;}int main(){    scanf("%d%d%d", &R, &C, &D);    for(int i = 1; i <= R; i++)  scanf("%s", m1[i]);    for(int i = 1; i <= R; i++)  scanf("%s", m2[i]);    s = 1;  t = s + R * C * 2 + 1;    for(int i = s; i <= t; i++)  head[i] = NULL;    for(int i = 1; i <= R; i++)        for(int j = 1; j <= C; j++){            if(m2[i][j-1] == 'L'){                  Sum ++;                Addedge(s, s+(i-1)*C+j, 1);                Addedge(s+(i-1)*C+j, s, 0);            }        }    for(int i = 1; i <= R; i++)        for(int j = 1; j <= C; j++){            for(int k = 0; k <= D; k++)                for(int w = 0; w <= D; w++){                    if(k + w < 1 || k + w > D)  continue;                    if(i + k > R || j + w > C || i - k < 1 || j - w < 1){                        Addedge(s+R*C+(i-1)*C+j, t, INF);                        Addedge(t, s+R*C+(i-1)*C+j, 0);                    }                    if(i + k <= R && j + w <= C){                        Addedge(s+R*C+(i-1)*C+j, s+(i+k-1)*C+j+w, INF);                        Addedge(s+(i+k-1)*C+j+w, s+R*C+(i-1)*C+j, 0);                    }                    if(i + k <= R && j - w > 0){                        Addedge(s+R*C+(i-1)*C+j, s+(i+k-1)*C+j-w, INF);                        Addedge(s+(i+k-1)*C+j-w, s+R*C+(i-1)*C+j, 0);                    }                    if(i - k > 0 && j + w <= C){                        Addedge(s+R*C+(i-1)*C+j, s+(i-k-1)*C+j+w, INF);                        Addedge(s+(i-k-1)*C+j+w, s+R*C+(i-1)*C+j, 0);                    }                    if(i - k > 0 && j - w > 0){                        Addedge(s+R*C+(i-1)*C+j, s+(i-k-1)*C+j-w, INF);                        Addedge(s+(i-k-1)*C+j-w, s+R*C+(i-1)*C+j, 0);                    }                }        }    for(int i = 1; i <= R; i++)        for(int j = 1; j <= C; j++){            Addedge(s+(i-1)*C+j, s+R*C+(i-1)*C+j, m1[i][j-1]-'0');            Addedge(s+R*C+(i-1)*C+j, s+(i-1)*C+j, 0);        }    printf("%d\n", Sum - MaxFlow());    return 0;}

[2星难度] 洛谷 P1129 [ZJOI2007]矩阵游戏 (二分图匹配)

题目传送门


题解

这题就是个裸的二分图匹配,之所以是2星难度,是因为转化模型不是特别好想。首先,我们考虑每一行,很容易看出,每一行最终只能保留一列作为最后起作用的那一个。然后我们发现如果某一行啥都没有就肯定是No。我们想到了?匹配。我们再分析一下。如果每一行归属的列确定了,列的任意交换是没有任何关系的,而且每个行选中的列不能相同,这样就一定能满足题目要求。行列一一对应。于是考虑行、列的二分图匹配

严谨一点,每个二分图匹配的答案必然可以成为答案,因为只要交换行就行了;每个答案也必然满足二分图匹配,若不满足就显然不是答案。我们交换行或列在二分图上没有任何关系,于是成功将问题转化,按题目连边然后直接看看二分图是否满流就行了。

这题其实还算是巧妙的,最后能转换为二分图模型的题目有很多,格子行和列(x,y坐标),格子黑白染色,奇偶性等等都可能迁移过来,GDKOI2017某一道题好像有些类似。。


代码

#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <algorithm>#include <cstring>#define maxn 100010#define maxm 1000010#define N 205#define INF 0x7FFFFFFFusing namespace std;int T, n, s, t, A[N][N], level[maxn], iter[maxn];int cur, head_p[maxn], q[maxn];struct List{    int next, obj, cap;}Edg[maxm];void Addedge(int a, int b, int c){    Edg[++cur].next = head_p[a];    Edg[cur].obj = b;    Edg[cur].cap = c;    head_p[a] = cur;}bool bfs(){    q[0] = s;    int head = 0, tail = 0;    for(int i = s; i <= t; i++)  level[i] = -1;    level[s] = 0;    while(head <= tail){        int now = q[head++];        for(int i = head_p[now]; ~ i; i = Edg[i].next){            int v = Edg[i].obj, c = Edg[i].cap;            if(c && level[v] == -1){                q[++tail] = v;                level[v] = level[now] + 1;            }        }    }    return level[t] != -1;}int Dinic(int now, int f){    if(now == t || !f)  return f;    int ret = 0;    for(int &i = iter[now]; ~ i; i = Edg[i].next){        int v = Edg[i].obj, c = Edg[i].cap;        if(c && level[now] < level[v]){            int d = Dinic(v, min(c, f));            ret += d;            f -= d;            Edg[i].cap -= d;            Edg[i^1].cap += d;            if(!f)  break;        }    }    return ret;}int MaxFlow(){    int flow = 0;    while(bfs()){        for(int i = s; i <= t; i++)  iter[i] = head_p[i];        flow += Dinic(s, INF);    }    return flow;}int main(){    scanf("%d", &T);    while(T --){        scanf("%d", &n);        s = 1;  t = s + n * 2 + 1;        for(int i = s; i <= t; i++)  head_p[i] = -1;        cur = -1;        for(int i = 1; i <= n; i++)            for(int j = 1; j <= n; j++){                scanf("%d", &A[i][j]);                if(A[i][j]){                    Addedge(s+i, s+n+j, INF);                    Addedge(s+n+j, s+i, 0);                }            }        for(int i = 1; i <= n; i++){            Addedge(s, s+i, 1);            Addedge(s+i, s, 0);            Addedge(s+n+i, t, 1);            Addedge(t, s+n+i, 0);        }        if(MaxFlow() == n)  puts("Yes");        else  puts("No");    }    return 0;}

[3星难度] 洛谷 P2053 [SCOI2007]修车 (最小费用最大流)

题目传送门


题解

这题我一开始想着将人拆成T个点,然后跑费用流,但是发现这样有点迷。处理了单独的费用,但是如何处理连边限制好像很难搞。于是我就从另一个角度入手,如果我知道每个人是修到第几辆车不就行了?于是我考虑将一个人拆成n个人,分别代表一个人在n个时间段的状态。一个人可能要先修某一辆车再去修另一辆,这使贡献的等待时间不同。但我们这样连边就解决了一切问题。这样修一辆车时,我们可以算出这个车主的等待时间,而且保证修车同时进行,在修一辆车的时候也不会有人去打扰他(流量限制)。于是答案就是最小的等待时间除以人数,而要求全部修完,就是最小费用最大流了。这题不能贪心能修则修,因为可以等待最快的人修完其他车才轮到你。

本题就是容量为1的费用流。连边就是s向每辆车连容量1,费用0。中间全部连(流出最佳方案),容量INF,费用为对应的时间乘时间段(修时也在等),这是这条流(即这个车主等待)的费用。我们不能算出这辆车对所有人的贡献,因为除了知道车主还在之外不能知道其他人走了没。这也告诉我们费用流要单独考虑一条流的费用。然后每个拆出来的人向t连边,容量1,费用0。跑一遍就得到答案了。

好久没写费用流,但是居然一次就写对了,真感动。(不就是个最短路吗,代码比Dinic短啊)


代码

#include <cstdio>#include <iostream>#include <algorithm>#include <cmath>#include <cstdlib>#include <cstring>#define maxn 100010#define maxm 1000010#define N 100#define INF 0x7FFFFFFFusing namespace std;int m, n, s, t, cur = -1;int head_p[maxn], q[maxn], preV[maxn], preE[maxn], flow[maxn], dis[maxn], T[N][N];bool vis[maxn];struct List{    int next, obj, cost, cap;}Edg[maxm];void Addedge(int a, int b, int c, int d){    Edg[++cur].next = head_p[a];    Edg[cur].obj = b;    Edg[cur].cap = c;    Edg[cur].cost = d;    head_p[a] = cur;}bool SPFA(){    for(int i = s; i <= t; i++)  dis[i] = INF, vis[i] = false;    q[0] = s;    vis[s] = true;    flow[s] = INF;    dis[s] = 0;    int head = 0, tail = 0;    while(head <= tail){        int now = q[head%maxn];        head ++;        for(int i = head_p[now]; ~ i; i = Edg[i].next){            int v = Edg[i].obj, c = Edg[i].cap, d = Edg[i].cost;            if(!c)  continue;            if(dis[now] + d < dis[v]){                dis[v] = dis[now] + d;                flow[v] = min(flow[now], c);                preE[v] = i;                preV[v] = now;                if(!vis[v]){                    vis[v] = true;                    tail ++;                    q[tail%maxn] = v;                    if(dis[q[head%maxn]] > dis[q[tail%maxn]])  swap(q[head%maxn], q[tail%maxn]);                }            }        }        vis[now] = false;    }    return dis[t] != INF;}int MaxFlowMinCost(){    int res = 0;    while(SPFA()){        res += dis[t] * flow[t];        for(int i = t; i != s; i = preV[i]){            Edg[preE[i]].cap -= flow[t];            Edg[preE[i]^1].cap += flow[t];        }    }    return res;}int main(){    scanf("%d%d", &m, &n);    s = 1;  t = s + n + m * n + 1;    for(int i = s; i <= t; i++)  head_p[i] = -1;    for(int i = 1; i <= n; i++)        for(int j = 1; j <= m; j++)            scanf("%d", &T[i][j]);    for(int i = 1; i <= n; i++){        Addedge(s, s+i, 1, 0);        Addedge(s+i, s, 0, 0);    }    for(int i = 1; i <= m; i++)        for(int j = 1; j <= n; j++){            Addedge(s+n+(i-1)*n+j, t, 1, 0);            Addedge(t, s+n+(i-1)*n+j, 0, 0);        }    for(int i = 1; i <= n; i++)        for(int j = 1; j <= m; j++)            for(int k = 1; k <= n; k++){                Addedge(s+i, s+n+(j-1)*n+k, INF, T[i][j]*k);                Addedge(s+n+(j-1)*n+k, s+i, 0, -T[i][j]*k);            }    printf("%.2lf\n", 1.00 * MaxFlowMinCost() / n);    return 0;}

[3星难度] 洛谷 P2805 植物大战僵尸 (最大权闭合子图+拓扑排序)

题目传送门


题解

终于有了压轴题的感觉。其实这题就是果的最大权闭合子图。APIO时听大神讲过的,当时记得很清楚,但是好像很难写。现在写了一下发现并不难。

首先吃了保护别人的植物才能吃别人,于是就是拿了A就必须拿B的模型,而如果全是正权能拿的拿完就好了,有负权就必须有所取舍的去决策。于是就套模型。将s连向正权植物,容量为权值,如果割掉代表失去正权。负权植物连向t,容量为权值相反数,如果割掉代表获得损失。每个植物向保护它的连边,容量为INF,代表选了这个就要选下一个,最小割割不断它们。

这样s集合代表选,t集合代表不选,中间不能割掉,而且任何一条s-t路径都会导致边的状态不确定,又拿了正权,又不想吃负权,于是这样的路径非法,要被割掉。于是割与方案就对应了。答案就是正权和-最小割。

但是,没有这么简单的哦,植物之间的保护可能成环,这时我们根本就不能获取它们的任何权值,甚至连向这个团的点的权值也不能拿到。我们需要来个拓扑排序搞掉所有的环以及连向环的点。这里不需要Tarjan,假如我们将边反向,拓扑一遍没标记的点就不会在网络里。为什么呢?因为拓扑排序能访问到某点的充要条件是它的入度可能变成0,而一个环无论怎样(除非缩起来),入度是不会通过其它边改成0的,而环搜不到,那环所指向的点的入度也不会变成0了。这些处于“铜墙铁壁”保护的植物就拥有不死之躯了。如此便与图完全对应了。

拓扑时边一定要反向!一开始错在这里。


代码

#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <iostream>#include <cmath>#define maxn 100100#define maxm 1000100#define INF 0x7FFFFFFFusing namespace std;bool ban[maxn];int n, m, s, t;int SUM, in[maxn], head_p[maxn], cnt = -1, cur = -1, level[maxn], iter[maxn], q[maxn], sco[maxn];struct EDGE{    EDGE *next;    int obj;}E[maxm], *HEAD[maxn];struct List{    int next, obj, cap;}Edg[maxm];void Insert(int a, int b){    E[++cnt].next = HEAD[a];    E[cnt].obj = b;    HEAD[a] = E+cnt;}void Addedge(int a, int b, int c){    Edg[++cur].next = head_p[a];    Edg[cur].obj = b;    Edg[cur].cap = c;    head_p[a] = cur;}bool bfs(){    int head = 0, tail = 0;    q[0] = s;    for(int i = s; i <= t; i++)  level[i] = -1;    level[s] = 0;    while(head <= tail){        int now = q[head++];        for(int i = head_p[now]; ~ i; i = Edg[i].next){            int v = Edg[i].obj, c = Edg[i].cap;            if(!c || level[v] != -1)  continue;            level[v] = level[now] + 1;            q[++tail] = v;        }    }    return level[t] != -1;}int Dinic(int now, int f){    if(now == t || !f)  return f;    int ret = 0;    for(int &i = iter[now]; ~ i; i = Edg[i].next){        int v = Edg[i].obj, c = Edg[i].cap;        if(!c || level[v] <= level[now])  continue;        int d = Dinic(v, min(f, c));        ret += d;        f -= d;        Edg[i].cap -= d;        Edg[i^1].cap += d;        if(!f)  break;    }    return ret;}int MinCut(){    int flow = 0;    while(bfs()){        for(int i = s; i <= t; i++)  iter[i] = head_p[i];        flow += Dinic(s, INF);    }    return flow;}void Topo(){    for(int i = 1; i <= n*m; i++)  ban[i] = true;    int head = 0, tail = 0;    for(int i = 1; i <= n*m; i++)        if(!in[i])  q[tail++] = i;    tail --;    while(head <= tail){        int now = q[head++];        ban[now] = false;        for(EDGE *p = HEAD[now]; p; p = p->next){            int v = p->obj;            in[v] --;            if(!in[v])  q[++tail] = v;        }    }}int main(){    scanf("%d%d", &n, &m);    s = 1;  t = s + n * m + 1;    for(int i = s; i <= t; i++)  head_p[i] = -1, HEAD[i] = NULL;    int w, x, y;    for(int i = 1; i <= n*m; i++){        scanf("%d", &sco[i]);        scanf("%d", &w);        for(int j = 1; j <= w; j++){              scanf("%d%d", &x, &y);            Insert(i, x*m+y+1);            in[x*m+y+1] ++;        }        if(i % m){              Insert(i+1, i);            in[i] ++;        }    }    Topo();    for(int i = 1; i <= n*m; i++)        for(EDGE *p = HEAD[i]; p; p = p->next){            int v = p->obj;            if(!ban[i] && !ban[v]){                Addedge(s+v, s+i, INF);                Addedge(s+i, s+v, 0);            }        }    for(int i = 1; i <= n*m; i++){        if(ban[i])  continue;        if(sco[i] > 0){            Addedge(s, s+i, sco[i]);            Addedge(s+i, s, 0);            SUM += sco[i];        }        else{            Addedge(s+i, t, -sco[i]);            Addedge(t, s+i, 0);        }    }    printf("%d\n", SUM - MinCut());    return 0;}

这里写图片描述

花非花
我心如明镜
但睁眼亦庆幸
唯独我不可以发声
如顽强疾病与命格相衬

——《众生》

原创粉丝点击