专题十一 网络流

专题十一 网络流

POJ 3436 ACM Computer Factory

题意: 题目没看懂,不写了

题解:

代码:

POJ 3281 Dining

题意: 每头奶牛都有自己喜欢的食物和饮料,并且不会食用其他不喜欢的食物和饮料。农夫约翰一共烹制了 F 种食物,并提供了 D 种饮料。约翰共有 N 头奶牛,其中第 i 头奶牛有 Fi 种喜欢的食物以及 Di 种喜欢的饮料。约翰需要给每头奶牛分配一种食物和一种饮料,并使得有吃有喝的奶牛数量尽可能大。每种食物或饮料都只有一份,所以只能分配给一头奶牛食用(即,一旦将第 2 种食物分配给了一头奶牛,就不能再分配给其他奶牛了)。1≤N,F,D≤100,1≤Fi≤F,1≤Di≤D
题解: 奶牛拆成奶牛1和奶牛2两种点,然后中间连一条权值为1的边;S->饮料,权值为1;饮料->奶牛1,权值为1;奶牛1->奶牛2,权值为1;奶牛2->食品,权值为1;食品->T,权值为1。然后跑dinic

Screenshot_20200924_195914.jpg代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 410, M = 40610, INF = 1e8;

int n, F, D, S, T;
int h[N], e[M], f[M], ne[M], idx;
int d[N], cur[N];

void add(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

int main() {
    scanf("%d%d%d", &n, &F, &D);
    S = 0, T = n * 2 + F + D + 1;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= F; i ++ ) add(S, n * 2 + i, 1);  // S->食品
    for (int i = 1; i <= D; i ++ ) add(n * 2 + F + i, T, 1);  // 饮料->T
    for (int i = 1; i <= n; i ++ ) {
        add(i, n + i, 1);  // 奶牛1->奶牛2
        int a, b, t;
        scanf("%d%d", &a, &b);
        while (a -- ) {
            scanf("%d", &t);
            add(n * 2 + t, i, 1);  // 食品->奶牛1
        }
        while (b -- ) {
            scanf("%d", &t);
            add(i + n, n * 2 + F + t, 1);  // 奶牛2->饮料
        }
    }
    printf("%d\n", dinic());
    return 0;
}

POJ 1087 A Plug for UNIX

题意: AS WE ALL KNOW,最近哈理工出现了一起着火事件,原因是用了杂牌的插座,有着强烈安全意识的鑫爹急忙为G808换上了最牛B的红牛插座。假设他买了n种插座,G808有m个电器,分别有自己可以插的插座。由于鑫爹的插座非常牛B,可以相互转换,接下来给出k行说明S1插座和S2插座把S2换成S1,那么最少有几个电器没有插座用呢?

题解: 源点S->插座,容量为1;插座->对应的电脑,容量为1;电脑->T,容量为1;转换器s->a,容量为INF。然后跑最大流,答案为n-maxflow

代码:

POJ 2195 Going Home

题意: 大佬突然兴致大发准备免费送房子,m表示人,H表示房子,每个房子只能进一个人,房子数等于人数。黄弘毅为了方便起见决定要让所有人到自己的房子的距离和最小,请问这个距离和是多少?

题解: 源点S->人,容量为1,费用为0;每个人到所有的房子 人->房子,容量为1,费用为距离;房子->T,容量为1,费用为0.让后跑最小费用最大流

代码:

POJ 2516 Minimum Cost

题意: 借个题面:https://blog.csdn.net/qq_31607947/article/details/70792595

有N个供应商,M个店主,K种物品。每个供应商对每种物品的的供应量已知,每个店主对每种物品的需求量的已知,从不同的供应商运送不同的货物到不同的店主手上需要不同的花费,又已知从供应商Mj送第kind种货物的单位数量到店主Ni手上所需的单位花费。问:供应是否满足需求?如果满足,最小运费是多少?0 < N, M, K < 50

输入如下:

题解: 对每一种货物都建图跑费用流,建一个汇点和源点,源点连向仓库,仓库连向商店,商店连向汇点。对于每一种货物作为源点Si, Si->仓库,容量为1,费用为0;仓库->商店,容量为1,费用为距离;商店->汇点T,容量为1,费用为0。然后每一种货物都建图跑费用流后,答案累加起来即可。

如果不这样建图,会导致建边数目特别多,tle

代码:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int const N = 210, M = 81000, INF = 1e9;
int e[M], h[M], w[M], idx, ne[M], d[N], incf[N], f[M], pre[N], st[N];  // d[i]到达i点的最小花费和,incf[i]到达i点的最大流
int n, m, S, T, need[N], sup[N], k, a[N][N], b[N][N], c[N][N][52];

void add(int a, int b, int c, int d) {
    e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx++;
}

// spfa找到最小花费的增广路
int spfa() {
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    queue<int> q;
    q.push(S);
    st[S] = 1;
    d[S] = 0;
    incf[S] = INF;
    while(q.size()) {
        int t = q.front();
        q.pop();
        st[t] = 0;
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (!f[i] || d[j] <= d[t] + w[i]) continue;  // 如果流为0或者花费变大,不走
            d[j] = d[t] + w[i];  // 更新花费
            pre[j] = i;  // 记录当前点的前一条边
            incf[j] = min(f[i], incf[t]);  // 更新最大流
            if (!st[j]) {
                q.push(j);
                st[j] = 1;
            }
        }
    }
    
    return incf[T] > 0;
}

int EK() {
    int flow = 0, cost = 0;
    while(spfa()) {  // 如果能够spfa找到最小花费的可增广流
        int t = incf[T];
        flow += t, cost += t * d[T];  // 更新最大流和最小花费
        for (int i = T; i != S; i = e[pre[i] ^ 1]) {
            f[pre[i]] -= t, f[pre[i] ^ 1] += t;  // 更新点
        }
    }
    return cost;
}

int main() {
    while (scanf("%d%d%d", &n, &m, &k) != EOF) {
        memset(need, 0, sizeof(need));
        memset(sup, 0, sizeof(sup));
        S = 0;
        T = n + m + 1;
        int flag = 0;
        if (n == 0 && m == 0 && k == 0) break;
        for (int i = 1; i <= n; i++)  //商店需要的
            for (int j = 1; j <= k; j++) {
                scanf("%d", &a[i][j]);
                need[j] += a[i][j];
            }
        for (int i = 1; i <= m; i++)  //仓库提供的
            for (int j = 1; j <= k; j++) {
                scanf("%d", &b[i][j]);
                sup[j] += b[i][j];
            }
        for (int i = 1; i <= k; i++)  //最小花费
            for (int j = 1; j <= n; j++)
                for (int l = 1; l <= m; l++) scanf("%d", &c[i][j][l]);
        for (int i = 1; i <= k; i++) {
            if (sup[i] < need[i]) {
                flag = 1;
            }
        }
        if (flag) {
            printf("-1\n");
            continue;
        }
        int ans = 0;
        for (int i = 1; i <= k; i++) {
            memset(h, -1, sizeof(h));
            idx = 0;
            for (int j = 1; j <= m; j++) add(S, j, b[j][i], 0);
            for (int j = 1; j <= n; j++) add(j + m, T, a[j][i], 0);

            for (int j = 1; j <= n; j++)
                for (int l = 1; l <= m; l++) {
                    add(l, j + m, INF, c[i][j][l]);
                }
            ans += EK();
        }
        printf("%d\n", ans);
    }
}

POJ 1459 Power Network

题意: 抄个题面:https://blog.csdn.net/winddreams/article/details/38665209

给出了n个点,其中np个节点是供电站,nc个节点是消耗,出给m条电线,每条线给出了(u,v)w代表u节点到v节点最多输送w,然后输入np个点(u)w,代表节点u是供电站,提供w的电量,nc个点(u)w节点u是消耗,消耗w。问最多消耗多少。

题解: 源点连接所有的供电站,每条线的容量是供电站的最大供电量,消耗点都连接汇点,容量是最大的消耗量,跑最大流即可。

代码:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
int const N = 200 + 10, M = 2e5 + 10, INF = 1e9 + 10;
int e[M * 2], ne[M * 2], h[N], f[M * 2], idx;
int d[N], cur[N], n, m, S, T, np, nc;

void add(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        int t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

int main() {
    char str[10];
    while (scanf("%d %d %d %d", &n, &np, &nc, &m) != EOF) {
        memset(h, -1, sizeof h);
        idx = 0;
        int u, v, w;
        S = n, T = n + 1;
        while (m--) {
            scanf("%s", str);
            sscanf(str, "(%d,%d)%d", &u, &v, &w);
            add(u, v, w);
        }
        while (np--) {
            scanf("%s", str);
            sscanf(str, "(%d)%d", &v, &w);
            add(S, v, w);
        }
        while (nc--) {
            scanf("%s", str);
            sscanf(str, "(%d)%d", &u, &w);
            add(u, T, w);
        }
        printf("%d\n", dinic());
    }
    return 0;
}

HDU 4280 Island Transport

题意: 在n个岛屿中,有m条双向航线,航线有单位时间内的运输上限,现在求从最左侧到最右侧的最大运输量

题解: 最大流模板题,但是dinic跑的很慢。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 1e5 + 10, M = 2e5 + 10, INF = 1e9 + 10;
int e[M * 2], ne[M * 2], h[N], f[M * 2], idx;
int d[N], cur[N], n, m, S, T, k;

void add(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while (q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit;
         i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i])
            continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs())
        while (flow = find(S, INF)) res += flow;
    return res;
}

inline int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> k;
    while (k--) {
        cin >> n >> m;
        int smin = INF, tmax = -INF;
        S = T = 1;
        int a, b, c;
        for (int i = 1; i <= n; i++) {
            cin >> a >> b;
            if (a <= smin) smin = a, S = i;
            if (a >= tmax) tmax = a, T = i;
        }
        idx = 0;
        memset(h, -1, sizeof h);
        while (m--) {
            cin >> a >> b >> c;
            add(a, b, c);  //正向边
            add(b, a, c);  //反向边
        }
        int ans = dinic();
        cout << ans << endl;
    }
    return 0;
}

HDU 4292 Food

题意: n个人,m1个食物,m2个饮料,要求每个人都拿到一份饮料和一份食物,求最大满足数。

题解: S向食物连边,流量为食物数量。饮料向T连边,流量为饮料数量。人拆点,限流为1,食物向人连边,流量为1,人向饮料连边,流量为1.

代码:

HDU 4289 Control

题意: 给一个无向图,有些不法分子要从S点T点,现在要抓住所有的不法分子阻止他们去T,那么就要控制某一些城市等待他们,控制每个城市花费不同,问最少花费是多少。

题解: 最小割。把每个点拆分为2个点,如果一个点不能走,就是断开这个点间的边,现在要S不能走到T,那么就是找到最小的割,使得图被分为2部分。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 600, M = 41000, INF = 1e9 + 10;
int e[M * 2], ne[M * 2], h[N], f[M * 2], idx;
int d[N], cur[N], n, m, S, T, val[N];

void add(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

int main() {
    while(scanf("%d%d", &n, &m) != EOF) {
        // i、i + n
        int s, t;
        memset(h, -1, sizeof h);
        idx = 0;
        scanf("%d%d", &s, &t);
        S = s, T = t + n;
        for (int i =1 ; i <= n; ++i) {
            scanf("%d", &val[i]);
            add(i, i + n, val[i]);
        }
        for (int i  =1 ; i <= m; ++i) {
            int a, b;
            scanf("%d%d", &a, &b);
            add(a + n, b, INF), add(b + n, a, INF);
        }
        printf("%d\n", dinic());
    }
    return 0;
}

UVA 10480 Sabotage

题意: 给出n个点m条边,每条边有一个花费,问将1和2隔离需要破坏的边的最小花费的边集。

题解: 最小割+打印割集,板题

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 110, M = 5210, INF = 1e9 + 10;
int e[M], ne[M], f[M], h[N], idx, st[N];
int d[N], cur[N], n, m, S, T;

void add(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

void dfs(int u) {
    st[u] = true;
    for (int i = h[u]; ~i; i = ne[i])
        if (!st[e[i]] && f[i])
            dfs(e[i]);
}

int main() {
    while(scanf("%d%d", &n, &m) != EOF) {
        if (!n && !m) break;
        S = 1, T = 2;
        memset(h, -1, sizeof h);
        memset(st, 0, sizeof st);
        idx = 0;
        for (int i = 1; i <= m; ++i) {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        dinic();

        dfs(S); 
        for (int i = 0; i < idx; i += 2) {
            int a = e[i ^ 1], b = e[i];
            if (st[a] && !st[b]) {
                printf("%d %d\n", a, b);
            }
        }

        printf("\n");
    }
    return 0;
}

HDU 2732 Leapin’ Lizards

题意: 给了n和d,表示有n行的图,(列数没给),对于每一个人都可以最长跳d的距离,对于每一个柱子有一个限定条件,只能从这里跳出去多少次。给了两张图,一张表示柱子的限定条件,一张给了现在那些柱子上有人。问有几个人不能跳到图的外面。

00000
01110
00000
.....
.LLL.
.....

T组测试数据,每组测试数据输入n,d,表示有两个n行的矩阵,保证列数相等但是没有输入是多少列。
第一个矩阵,每个点有一根柱子,每个柱子的高度<10。
第二个矩阵,‘L’表示该点有一只蜥蜴,‘.’表示啥也没有。
由于作用力与反作用力的原因,柱子上每当有一只蜥蜴跳过柱子高度就会减少1,柱子高度为0时不能再有蜥蜴跳过。蜥蜴跳跃距离为d,行数之差+列数之差距离不超过d即可进行跳跃。

题解:

网格中每个点拆分为2个点,p1和p2,p1->p2,流量为柱子高度
源点S->网格中每个点的p1连边,流量为1;
p1->向每个可以跳到的点的p1连边,流量为INF
如果p1能够直接跳出,p1->汇点T,流量为INF
跑最大流即可

代码:

HDU 3338 Kakuro Extension

题意: 一张图里有黑色格子和白色格子,白色格子为待填的格子,而黑色格子有2半,左下部分为这个点向下直到下个黑点或者边界中的一列白格填的总数,右上部分为这个点向右直到下个黑点或者边界中的一行白格填的总数。现在要你将白色的格子填上数字,使得满足给定的黑色格子框架。 2 ≤ n , m ≤ 100 2 ≤ n,m ≤ 100 2n,m100

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KcPG6Syl-1616151629698)(https://i.loli.net/2021/03/19/cyBaKGesn3H68pz.png)]

题解: 最大流。由于每个白色格子填的数字都对所在行和所在列的黑色格子有贡献,因此网格白色格子填数和=黑色格子的和。下面考虑建图:
因为每个白色格子的填数范围为1 - 9,这样就变为上下界可行流了,考虑范围往下减一,然后答案加一即可,因此范围变为0 - 8
每个黑色格子分为两个部分b1和b2,b1为左下部分,b2为右上部分。
源点S->每个黑色格子的b1,流量为b1权值 - 到下一个黑色格子距离的白色格子数目(因为白色格子范围变为0 - 8了)
每个黑色格子的b2->汇点T,流量为b2权值 - 到下一个黑色格子距离的白色格子数目(因为白色格子范围变为0 - 8了)
对于每个白色格子来说:
当前白色格子左边的第一个黑色格子的b2->白色格子,权值为8(因为白色格子范围变为0 - 8了)
当前白色格子的上面第一个黑色格子的b1->白色格子,权值为8(因为白色格子范围变为0 - 8了)
然后跑最大流即可

借一下这个老哥的图和代码:https://blog.csdn.net/wyxxzsy/article/details/83005760

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cxgivpg8-1616151629702)(https://i.loli.net/2021/03/19/HofqMn2WLds6mCl.png)]

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
const int MAXN = 21000;     //点数的最大值
const int MAXM = 1e7 + 10;  //边数的最大值
const int INF = 0x3f3f3f3f;
struct Edge {
    int to, next, cap, flow;
} edge[MAXM];  //注意是MAXM
int tol;
int head[MAXN];
int gap[MAXN], dep[MAXN], cur[MAXN];
void init() {
    tol = 0;
    memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w, int rw = 0) {
    edge[tol] = Edge{v, head[u], w, 0};
    head[u] = tol++;
    edge[tol] = Edge{u, head[v], rw, 0};
    head[v] = tol++;
}
int Q[MAXN];
void BFS(int start, int end) {
    memset(dep, -1, sizeof(dep));
    memset(gap, 0, sizeof(gap));
    gap[0] = 1;
    int front = 0, rear = 0;
    dep[end] = 0;
    Q[rear++] = end;
    while (front != rear) {
        int u = Q[front++];
        for (int i = head[u]; i != -1; i = edge[i].next) {
            int v = edge[i].to;
            if (dep[v] != -1) continue;
            Q[rear++] = v;
            dep[v] = dep[u] + 1;
            gap[dep[v]]++;
        }
    }
}
int S[MAXN];
int sap(int start, int end, int N) {
    BFS(start, end);
    memcpy(cur, head, sizeof(head));
    int top = 0;
    int u = start;
    int ans = 0;
    while (dep[start] < N) {
        if (u == end) {
            int Min = INF;
            int inser;
            for (int i = 0; i < top; i++)
                if (Min > edge[S[i]].cap - edge[S[i]].flow) {
                    Min = edge[S[i]].cap - edge[S[i]].flow;
                    inser = i;
                }
            for (int i = 0; i < top; i++) {
                edge[S[i]].flow += Min;
                edge[S[i] ^ 1].flow -= Min;
            }
            ans += Min;
            top = inser;
            u = edge[S[top] ^ 1].to;
            continue;
        }
        bool flag = false;
        int v;
        for (int i = cur[u]; i != -1; i = edge[i].next) {
            v = edge[i].to;
            if (edge[i].cap - edge[i].flow && dep[v] + 1 == dep[u]) {
                flag = true;
                cur[u] = i;
                break;
            }
        }
        if (flag) {
            S[top++] = cur[u];
            u = v;
            continue;
        }
        int Min = N;
        for (int i = head[u]; i != -1; i = edge[i].next)
            if (edge[i].cap - edge[i].flow && dep[edge[i].to] < Min) {
                Min = dep[edge[i].to];
                cur[u] = i;
            }
        gap[dep[u]]--;
        if (!gap[dep[u]]) return ans;
        dep[u] = Min + 1;
        gap[dep[u]]++;
        if (u != start) u = edge[S[--top] ^ 1].to;
    }
    return ans;
}
int G[110][110][2];  // 0--下 1--右

char tmp[10];

int shift(int x) {
    int s, e;
    if (x == 1)
        s = 0, e = 2;
    else
        s = 4, e = 6;
    int ans = 0;
    for (int i = s; i <= e; i++) {
        ans = ans * 10 + tmp[i] - '0';
    }
    return ans;
}

int n, m;
int right(int x, int y) {
    int i = 1;
    while (y + i <= m && G[x][y + i][0] == -2) {
        i++;
    }
    return i - 1;
}
int down(int x, int y) {
    int i = 1;
    while (x + i <= n && G[x + i][y][0] == -2) {
        i++;
    }
    return i - 1;
}
// int G[MAXN][MAXN][2];//0--下 1--右

int upb(int x, int y) {  // 1
    int i = -1;
    while (x + i > 0 && G[x + i][y][0] == -2) {
        i--;
    }
    return x + i;
}
int leftb(int x, int y) {  // 0
    int i = -1;
    while (y + i > 0 && G[x][y + i][0] == -2) {
        i--;
    }
    return y + i;
}
void show() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            cout << G[i][j][0] << " " << G[i][j][1] << "  ";
        cout << endl;
    }
}
// int G[MAXN][MAXN][2];//0--下 1--右

int eee(int x, int y) {
    int pos = (x - 1) * m + y;
    for (int u = head[pos]; ~u; u = edge[u].next) {
        return edge[u].flow;
    }
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        init();
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                scanf("%s", tmp);
                if (tmp[0] == '.')
                    G[i][j][0] = G[i][j][1] = -2;
                else {
                    if (tmp[0] == 'X')
                        G[i][j][0] = -1;
                    else {
                        G[i][j][0] = shift(1);
                    }
                    if (tmp[4] == 'X')
                        G[i][j][1] = -1;
                    else
                        G[i][j][1] = shift(0);
                }
            }
        }
        //先处理权值
        //源点
        //汇点
        int s = 0, e = 2 * n * m + 1;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) {
                if (G[i][j][0] > 0) {  //是个黑点
                    G[i][j][0] -= down(i, j);  // 白色范围变了,所有需要减掉白色格子的数目
                    // cout<<G[i][j][0]<<" "<<i<<" "<<j<<endl;
                    addedge(n * m + (i - 1) * m + j, e, G[i][j][0]);  // 每个黑色格子的右上角格子向汇点连边
                }
                if (G[i][j][1] > 0) {  //是个黑点
                    G[i][j][1] -= right(i, j);
                    // cout<<G[i][j][1]<<" "<<i<<" "<<j<<endl;
                    addedge(s, (i - 1) * m + j, G[i][j][1]);  // 源点向每个黑色格子的左下角连边
                }
            }
        //对白点加边
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) {
                if (G[i][j][0] == -2) {
                    int yy = leftb(i, j);
                    int xx = upb(i, j);
                    addedge((i - 1) * m + yy, (i - 1) * m + j, 8);  // 白色左侧的黑色向白色连边
                    // cout << i << " " << yy << "     " << i << " " << j << endl;
                    addedge((i - 1) * m + j, n * m + (xx - 1) * m + j, 8);  // 白色向上面的黑色连边
                    // cout << i << " " << j << "     " << xx << " " << j << endl;
                }
            }
        int flow = sap(s, e, e + 1);
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (G[i][j][0] != -2) printf("_%c", " \n"[j == m]);
                if (G[i][j][0] == -2) {
                    printf("%d%c", 1 + eee(i, j), " \n"[j == m]);
                }
            }
        }
    }
    return 0;
}

HDU 3605 Escape

题意: 给出每个人适合住的星球信息和该星球能住多少人 ,第一行给出n m 代表有 n 个人 m 个星球,然后接下来n行每行m个数字 1代表适合第 i 个星球 0 代表不适合第 i 个星球,最后一行m个数表示第 i 个星球最多可以住多少个人,问是不是所有人都可以住到星球上。每个星球最多人数ai 0 < = a i < = 100000 0 <= ai <= 100000 0<=ai<=100000,人数n: 1 < = n < = 100000 1 <= n <= 100000 1<=n<=100000, 星球数目m: 1 < = m < = 10 1<=m<=10 1<=m<=10

题解: 因为星球数目只有1000个,这样就会导致有很多人选择的星球情况是一样的。因此我们可以把点从人变为状态,同一个状态连边的情况是一样的,然后只要改变这个点入边的权值即可。
建图如下:
每个星球 -> 汇点T连边,流量为星球承载人数ai
源点S -> 每个状态点连边,流量为当前这个状态的出现次数
每个状态 -> 对应的星球连边,流量为正无穷INF

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 1e4 + 10, M = 1e5 + 10, INF = 1e9 + 10;
int e[M * 2], ne[M * 2], h[N], f[M * 2], idx;
int d[N], cur[N], n, m, S, T;

void add(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

int peo[1111];
int val[55];
int main() {
    int num, m;
    int u, v;
    while (scanf("%d%d", &num, &m) != EOF) {
        memset(h, -1, sizeof h);
        idx = 0;
        memset(peo, 0, sizeof(peo));
        S = 1;
        T = 1 + (1 << m) + m + 1;
        n = T;
        for (int i = 0; i < num; i++) {
            int t = 0;
            for (int j = 0; j < m; j++) {
                scanf("%d", &u);
                if (u) t += (1 << j);
            }
            peo[t]++;  // 计算每种状态的人数
        }
        for (int i = 0; i < m; i++) {
            scanf("%d", &val[i]);  // 每个星球向汇点连边,流量为星球承载人数
            add(i + (1 << m) + 2, T, val[i]);
        }
        for (int i = 0; i < (1 << m); i++) {
            if (peo[i] == 0) continue;
            add(S, i + 2, peo[i]);  // 源点向每个状态点连边,流量为当前这个状态的出现次数
            for (int j = 0; j < m; j++) {
                int k = 1 << j;
                if (i & k) add(i + 2, j + (1 << m) + 2, INF);  // 每个状态向对应的星球连边,流量为正无穷
            }
        }
        int ans = dinic();
        if (ans >= num)
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}

HDU 3081 Marriage Match II

题意: n个女生与n个男生配对,每个女生只能配对某些男生,有些女生相互是朋友,每个女生也可以跟她朋友能配对的男生配对。每次配对,每个女生都要跟不同的男生配对且每个女生都能配到对。问最多能配对几轮。

题解: n个女生与n个男生配对,每个女生只能配对某些男生,有些女生相互是朋友,每个女生也可以跟她朋友能配对的男生配对。每次配对,每个女生都要跟不同的男生配对且每个女生都能配到对。问最多能配对几轮。
二分+最大流。二分轮数,如果第k轮能够完全匹配,那么等价于一个女生能够匹配k人,每个男生能够匹配k个女生。
因此建图如下:
源点S->每个女生,流量为二分的mid
每个女生->可以匹配的男生,流量为1
男生->汇点T,流量为二分的mid
同时,如果女生a和b可以共享一个男生且a->男生t,则也要连一条边b->男生t,流量为1,这个操作可以使用并查集实现

代码:

代码写了很久没通,附上大佬的吧:https://blog.csdn.net/qq_41090676/article/details/98958122

#include <bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int maxn = 210;
const int maxm = 21000;
bool bridge[maxn][maxn];
int pre[maxn];

struct Edge {
    int from, to, cap, flow;
};

struct Dinic {
    Edge edge[maxm];
    int next[maxm], head[maxn], deep[maxn], cur[maxn], vis[maxn], n, m, s, t, cnt = 0;

    void init_point(int nn, int mm, int ss, int tt) {
        n = nn, m = mm, s = ss, t = tt;
    }

    void init() {
        memset(head, -1, sizeof(head));
        cnt = 0;
    }

    void addEdge(int u, int v, int w) {
        edge[cnt].cap = w, edge[cnt].flow = 0, edge[cnt].from = u;
        edge[cnt].to = v, next[cnt] = head[u], head[u] = cnt++;
        edge[cnt].cap = 0, edge[cnt].flow = 0, edge[cnt].from = v, edge[cnt].to = u, next[cnt] = head[v], head[v] = cnt++;
    }

    void build(int value) {
        init();
        for (int i = 1; i <= n; i++) addEdge(s, i, value);
        for (int i = n + 1; i <= 2 * n; i++) addEdge(i, t, value);
        for (int i = 1; i <= n; i++) {
            for (int j = n + 1; j <= 2 * n; j++) {
                if (bridge[i][j]) addEdge(i, j, 1);
            }
        }
    }

    bool bfs() {
        memset(vis, 0, sizeof(vis));
        memset(deep, 0, sizeof(deep));
        queue<int> q;
        deep[s] = 0;
        vis[s] = true;
        q.push(s);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (int i = head[u]; i != -1; i = next[i]) {
                Edge& e = edge[i];
                if (!vis[e.to] && e.cap > e.flow) {
                    deep[e.to] = deep[u] + 1;
                    vis[e.to] = true;
                    q.push(e.to);
                }
            }
        }
        return vis[t];
    }

    int dfs(int u, int in) {
        if (u == t || in == 0) return in;
        int out = 0, f;
        for (int& i = cur[u]; i != -1; i = next[i]) {
            int v = edge[i].to;
            if (deep[v] == deep[u] + 1 &&
                (f = dfs(v, min(in, edge[i].cap - edge[i].flow))) > 0) {
                edge[i].flow += f;
                edge[i ^ 1].flow -= f;
                out += f;
                in -= f;
                if (in == 0) break;
            }
        }
        return out;
    }

    int maxflow() {
        int ans = 0;
        while (bfs()) {
            for (int i = 0; i <= 2 * n + 1; i++) cur[i] = head[i];
            ans += dfs(s, INF);
        }
        return ans;
    }

} DC;

int findset(int x) {
    if (x == pre[x]) return x;
    return pre[x] = findset(pre[x]);
}

void unions(int a, int b) {
    int x = findset(a);
    int y = findset(b);
    if (x != y) pre[x] = y;
}

int main() {
    int T, f, n, m, x, y;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d%d", &n, &m, &f);
        DC.init_point(n, m, 0, 2 * n + 1);
        memset(bridge, 0, sizeof(bridge));
        for (int i = 0; i < maxn; i++) 
            pre[i] = i;
        for (int i = 0; i < m; i++) {
            scanf("%d%d", &x, &y);
            bridge[x][y + n] = true;
        }
        for (int i = 0; i < f; i++) {
            scanf("%d%d", &x, &y);
            unions(x, y);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = i + 1; j <= n; j++) {
                if (findset(i) == findset(j)) {
                    for (int k = n + 1; k <= 2 * n; k++) {
                        if (bridge[i][k] || bridge[j][k]) {
                            bridge[i][k] = bridge[j][k] = true;
                        }
                    }
                }
            }
        }
        int l = 0, r = 100, ans, mid;
        while (l <= r) {
            mid = (l + r) >> 1;
            DC.build(mid);
            if (DC.maxflow() == n * mid) {
                ans = mid;
                l = mid + 1;
            } else r = mid - 1;
        }
        printf("%d\n", ans);
    }
    return 0;
}

HDU 3416 Marriage Match IV

题意: 有 n 个城市,知道了起点和终点,有 m 条有向边,问从起点到终点的最短路一共有多少条。(一条边只能走一次)$2<=n<=1000, 0<=m<=100000 $

题解: 最短路+网络流。先通过最短路,然后将不在最短路上边删掉,判断一条边是在最短路上:dis[a] + c == dis[b]。然后重新建图,每条在最短路上的边权值改为1,然后跑最大流即可。

代码:

#include <bits/stdc++.h>

using namespace std;

#define int long long
typedef pair<int, int> PII;
int const N = 1050, M = 200500, INF = 1e18 + 10;
int e[M], ne[M], w[M], h[N], idx, n, m, dis[N], st[N], TT, T, S, f[M];
int d[N], cur[N];
struct Edge {
    int u, v, c;
}edge[M];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void add2(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

// 堆优化版dijksyta
void dijkstra() {
    for (int i = 1; i <= n; ++i) dis[i] = 0x3f3f3f3f3f3f3f3f, st[i] = 0;
    priority_queue<PII, vector<PII>, greater<PII> >
        q;  // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点
    dis[S] = 0;         // 一开始源点距离为0
    q.push({0, S});     // 把源点信息放入队列
    while (q.size()) {  // 每个点只出入队列一次
        auto t = q.top();
        q.pop();

        int distance = t.first, ver = t.second;  // 最小距离和相对应的点
        if (st[ver])
            continue;  // 这个操作保证每个点只出入队一次,因为队列里面可能会出现{dis1[3],
                       // 3}, {dis2[3],
                       // 3}的情况,这样保证dis1[3]<dis2[3]时,3号点只进出入队一次
        st[ver] = 1;  // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次

        for (int i = h[ver]; ~i; i = ne[i]) {  // 遍历ver的邻接点
            int j = e[i];
            if (dis[j] > distance + w[i]) {
                dis[j] = distance + w[i];
                q.push(
                    {dis[j],
                     j});  // 这里不需要判断st,因为一旦更新发现更小必须放入队列
            }
        }
    }
}

signed main() {
    cin >> TT;
    while(TT--) {
        cin >> n >> m;
        idx = 0;
        for (int i = 1; i <= n; ++i) h[i] = -1;
        for (int i = 1, a, b, c; i <= m; ++i) {  // 读入m条边
            scanf("%lld%lld%lld", &a, &b, &c);
            edge[i] = {b, a, c};
            add(a, b, c);
        }
        cin >> S >> T;
        dijkstra();

        idx = 0;
        for (int i = 1; i <= n; ++i) h[i] = -1;
        for (int i = 1; i <= m; ++i) {
            int a = edge[i].v, b = edge[i].u, c = edge[i].c;
            if (dis[a] + c == dis[b]) add2(a, b, 1);
        }

        printf("%lld\n", dinic());
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值