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

前言

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


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

题目传送门


题解

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

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


代码

#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 100010
#define maxm 1000010
#define INF 0x7FFFFFFF

using 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 
#include 
#include 
#include 
#include 
#include 
#define maxn 100010
#define maxm 1000010
#define N 100
#define INF 0x7FFFFFFF

using 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 
#include 
#include 
#include 
#include 
#include 
#define maxn 100010
#define maxm 1000010
#define N 205
#define INF 0x7FFFFFFF

using 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 
#include 
#include 
#include 
#include 
#include 
#define maxn 100010
#define maxm 1000010
#define N 100
#define INF 0x7FFFFFFF

using 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 
#include 
#include 
#include 
#include 
#include 
#define maxn 100100
#define maxm 1000100
#define INF 0x7FFFFFFF

using 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;
}

网络流水题五杀——洛谷 P3701、P2472、P1129、P2053、P2805_第1张图片

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

——《众生》

你可能感兴趣的:(网络流,&,线性规划,图论)