HihoCoder上网络流算法题目建模总结

来源:互联网 发布:微信抢红包开挂软件 编辑:程序博客网 时间:2024/06/05 00:41
 

HihoCoder上网络流算法题目建模总结

标签: 网络流
 479人阅读 评论(0) 收藏 举报

目录(?)[+]

经过了几天的学习和做题,我利用刘汝佳书上的网络流算法模板完成了HihoCoder上的几个网络流算法,HihoCoder可能还会继续更新网络流算法,所以我也会接着总结。

这个主要是对网络流算法的建模做分析和理解,不具体分析网络流算法,网络流算法会单独总结。

网络流一·Ford-Fulkerson算法

题目连接

本题没有建模,就是标准的网络最大流求解,将图建完后直接应用最大流算法即可解决。但在此记录几点注意的地方:

  • 所谓的“残留网络”就是为了让程序在遍历时可以会推所添加的记录流量差的反向边。比如 a–>b 容量为10,流量为3,其意义为从a到b已经走了3个流量,还有7个流量可以走过去,3个流量可以再退回来。

  • 增广路径就是找从 s 到 t 的能通过的路径,所谓能通过就是还存在未满流的边可以再走一些流量。这类增广路径算法的思想就是不断地在“残留网络”上找“增广路径”,然后修改残留网络上的流量,直到不通为止。

代码如下,为刘汝佳书《算法竞赛入门经典》中一个模板:

const int maxn = 505;const int INF = 0x7fffffff;struct Edge {    int from, to, cap, flow;    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};struct EdmondsKarp {    int n, m;    vector<Edge> edges;    vector<int> G[maxn];    int a[maxn];    int p[maxn];    void init(int n) {        for (int i = 0; i < n; ++i) G[i].clear();        edges.clear();    }    void AddEdge(int from, int to, int cap) {        edges.push_back(Edge(from, to, cap, 0));        edges.push_back(Edge(to, from, 0, 0));  // reverse edge        m = edges.size();        G[from].push_back(m - 2);        G[to].push_back(m - 1);    }    int Maxflow(int s, int t) {        int flow = 0;        for (;;) {            memset(a, 0, sizeof(a));            queue<int> Q;            Q.push(s);            a[s] = INF;            while (!Q.empty()) {                int x = Q.front(); Q.pop();                for (int i = 0; i < G[x].size(); ++i) {                    Edge &e = edges[G[x][i]];                    if (!a[e.to] && e.cap > e.flow) {                        p[e.to] = G[x][i];                        a[e.to] = min(a[x], e.cap - e.flow);                        Q.push(e.to);                    }                }                if (a[t]) break;            }            if (!a[t]) break;            for (int u = t; u != s; u = edges[p[u]].from) {                edges[p[u]].flow += a[t];                edges[p[u] ^ 1].flow -= a[t];            }            flow += a[t];        }        return flow;    }};int main(){#ifdef LOCAL    freopen("input.txt", "r", stdin);    // freopen("sorted.txt", "w", stdout);#endif    int N, M, u, v, c;    cin >> N >> M;    EdmondsKarp ek;    // construct the graph    ek.init(N);    for (int i = 0; i < M; ++i) {        cin >> u >> v >> c;        ek.AddEdge(u, v, c);    }    cout << ek.Maxflow(1, N) << endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

网络流二·最大流最小割定理

题目连接

这部分主要是证明最小割等于最大流,证明详细步骤见上面题目,这里记录下主要步骤:

  • f(S, T) 等于从 s 出来的流,等于当前的网络流量 f。f(S, T) 表示割 (S, T)的净流量。

  • 对于网络的任何一个流,一定小于等于任何一个割的容量(f(S, T) <= C(S, T)

对于一个网络 G=(V, E),有源点 s 汇点 t,以下三个等价: 
1、f 是图 G 的最大流 
2、残留网络不存在增广路 
3、对于G的一个割(S, T),此时 f = C(S, T) 
证明: 
1=>2:假设 f 是图 G 的最大流,如果残留网络存在增广路 p,流量为 fp,那么有流 f’ = f + fp > f ,与 f 是最大流矛盾。 
2=>3:对于任意的 u S v T,有 f(u, v) = c(u, v),即

f(u,v)=c(u,v)=f(S,T)=C(S,T)=f

这样,找不到增广路的时候求得的一定是最大流,最大流等于最小割。

另外,本题要求求出最小割集合 S,在割 (S, T) 中,计算出的残留网络从 s 开始遍历,所能遍历到的点即为 S 集合,因为求得的最小割就是最大流,最大流中残留网络不存在增广路径,也就是说从 s 没法走到 t,故从 s 开始遍历,所得到的点的集合就是 S。

const int maxn = 505;const int INF = 0x7FFFFFFF;struct Edge {    int from, to, cap, flow;    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};bool used[maxn];std::vector<int> rst;struct EdmondsKarp {    int n, m;    vector<Edge> edges;    vector<int> G[maxn];    int a[maxn];    int p[maxn];    void init(int n) {        for (int i = 0; i < n; ++i) G[i].clear();        edges.clear();    }    void AddEdge(int from, int to, int cap) {        edges.push_back(Edge(from, to, cap, 0));        edges.push_back(Edge(to, from, 0, 0));  // reverse edge        m = edges.size();        G[from].push_back(m - 2);        G[to].push_back(m - 1);    }    int Maxflow(int s, int t) {        int flow = 0;        for (;;) {            memset(a, 0, sizeof(a));            queue<int> Q;            Q.push(s);            a[s] = INF;            while (!Q.empty()) {                int x = Q.front(); Q.pop();                for (int i = 0; i < G[x].size(); ++i) {                    Edge &e = edges[G[x][i]];                    if (!a[e.to] && e.cap > e.flow) {                        p[e.to] = G[x][i];                        a[e.to] = min(a[x], e.cap - e.flow);                        Q.push(e.to);                    }                }                if (a[t]) break;            }            if (!a[t]) break;            for (int u = t; u != s; u = edges[p[u]].from) {                edges[p[u]].flow += a[t];                edges[p[u] ^ 1].flow -= a[t];            }            flow += a[t];        }           return flow;    }       void GetMinCutSetS(int s) {        rst.push_back(s); used[s] = true;        for (int i = 0; i < G[s].size(); ++i) {            Edge &e = edges[G[s][i]];            if (!used[e.to] && e.flow != e.cap) {                GetMinCutSetS(e.to);            }        }    }};int main(int argc, char** argv) {#ifdef LOCAL    freopen("input.txt", "r", stdin);    freopen("output.txt", "w", stdout);#endif    // 2 <= N <= 500, 1 <= M <= 20000    int N, M; cin >> N >> M;    EdmondsKarp ek;    ek.init(N);    // construct the graph    int u, v, c;    for (int i = 0; i < M; ++i) {        cin >> u >> v >> c;        ek.AddEdge(u, v, c);    }    int flow = ek.Maxflow(1, N);    ek.GetMinCutSetS(1);    std::cout << flow << " " << rst.size() << std::endl;    for (int i = 0; i < rst.size() - 1; ++i) {        cout << rst[i] << " ";    }    std::cout << rst[rst.size() - 1] << std::endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

说明:代码中的 GetMinCutSetS 就是一个 DFS 方法,从一个点开始遍历得到最终的 S 集合,没什么难的。结果保存在一个 vector 中。

网络流三·二分图多重匹配

题目连接

二分图的多重匹配,其实质就是需要规定 X 集中的点可以使用多少次,Y 中的点可以重用多少次,如果 X 中的某个点的流量使用完毕,则这条边满流,则不可再次使用。从源点 s 指向 X 集中的边的容量则规定了这个点能用多少次!Y 集中的指向汇点 t 的边的容量也是如此含义。所以,如果 Y 集中的容量没有用光,则说明当前的流(匹配)还没有达到所期望的要求。 
这题使用了CheckMaxMatch 用来判断指向汇点的边是否满流。

const int maxn = 1005;const int INF = 0x7FFFFFFF;struct Edge {    int from, to, cap, flow;    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};// 求最小割所用到的两个// 求最小割点的思路为在原来求最大流的残留网络上从 s 点开始 DFS,所有能遍历到的点都是 S 集合里面的,// 剩余没有遍历到的点就是 T 集合里的点。// bool used[maxn];// std::vector<int> rst;struct EdmondsKarp {    int n, m;    vector<Edge> edges;    vector<int> G[maxn];    int a[maxn];    int p[maxn];    void init(int n) {        for (int i = 0; i < n; ++i) G[i].clear();        edges.clear();    }    void AddEdge(int from, int to, int cap) {        edges.push_back(Edge(from, to, cap, 0));        edges.push_back(Edge(to, from, 0, 0));  // reverse edge        m = edges.size();        G[from].push_back(m - 2);        G[to].push_back(m - 1);    }    int Maxflow(int s, int t) {        int flow = 0;        for (;;) {            memset(a, 0, sizeof(a));            queue<int> Q;            Q.push(s);            a[s] = INF;            while (!Q.empty()) {                int x = Q.front(); Q.pop();                for (int i = 0; i < G[x].size(); ++i) {                    Edge &e = edges[G[x][i]];                    if (!a[e.to] && e.cap > e.flow) {                        p[e.to] = G[x][i];                        a[e.to] = min(a[x], e.cap - e.flow);                        Q.push(e.to);                    }                }                if (a[t]) break;            }            if (!a[t]) break;            for (int u = t; u != s; u = edges[p[u]].from) {                edges[p[u]].flow += a[t];                edges[p[u] ^ 1].flow -= a[t];            }            flow += a[t];        }        return flow;    }    bool CheckMaxMatch(int N, int M) {        for (int i = 1; i <= M; ++i) {            for (int j = 0; j < G[N + i].size(); ++j) {                Edge &e = edges[G[N + i][j]];                if (e.flow != e.cap && e.flow > 0) { return false; }            }        }        return true;    }    /*     // 遍历求网络的最小割中的 S 集合点,结果储存在上面的 vector<int> rst 中;    void GetMinCutSetS(int s) {        rst.push_back(s); used[s] = true;        for (int i = 0; i < G[s].size(); ++i) {            Edge &e = edges[G[s][i]];            if (!used[e.to] && e.flow != e.cap) {                GetMinCutSetS(e.to);            }        }    }    */};int main(int argc, char** argv) {#ifdef LOCAL    freopen("input.txt", "r", stdin);    freopen("output.txt", "w", stdout);#endif    int T; cin >> T;    while (T--) {        int N, M; cin >> N >> M;        EdmondsKarp ek;        ek.init(N + M + 2);        int m[maxn + 5], a[maxn + 5], b[maxn + 5];        for (int i = 0; i < M; ++i) cin >> m[i];        for (int i = 0; i < N; ++i) {            cin >> a[i] >> b[i];            int tmprecv;            for (int j = 0; j < b[i]; ++j) {                cin >> tmprecv;                // X -> Y                ek.AddEdge(i + 1, tmprecv + N, 1);            }        }        for (int i = 1; i <= N; ++i) {            ek.AddEdge(0, i, a[i - 1]);        }        for (int i = 1; i <= M; ++i) {            ek.AddEdge(N + i, N + M + 1, m[i - 1]);        }        ek.Maxflow(0, N + M + 1);        cout << (ek.CheckMaxMatch(N, M) ? "Yes" : "No") << endl;    }    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121

网络流四·最小路径覆盖

题目连接

建图的方法为: 
1、添加源点 s 和汇点 t。 
2、拆点,将每个点拆成两个点,比如 a 拆成 a1, a2,b 拆成 b1, b2。 
3、从源点向 X 集合中每个点添加一条容量为 1 的有向边。 
4、从 Y 集合向汇点中每个点添加一条容量为 1 的有向边。 
5、如果 a -> b 有边,则从 a1 向 b2 添加一条容量为 1 的有向边。

最小路径覆盖就是总点数 N - 最小割。证明在我学会之前暂时不写。

推荐去看《计算机算法设计与分析》中的网络流 24 题中的魔术球问题,这是一道很隐晦的利用网络流的最小路径覆盖问题,很经典。

const int maxn = 1005;const int INF = 0x7FFFFFFF;struct Edge {    int from, to, cap, flow;    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};// 求最小割所用到的两个// 求最小割点的思路为在原来求最大流的残留网络上从 s 点开始 DFS,所有能遍历到的点都是 S 集合里面的,// 剩余没有遍历到的点就是 T 集合里的点。// bool used[maxn];// std::vector<int> rst;struct EdmondsKarp {    int n, m;    vector<Edge> edges;    vector<int> G[maxn];    int a[maxn];    int p[maxn];    void init(int n) {        for (int i = 0; i < n; ++i) G[i].clear();        edges.clear();    }    void AddEdge(int from, int to, int cap) {        edges.push_back(Edge(from, to, cap, 0));        edges.push_back(Edge(to, from, 0, 0));  // reverse edge        m = edges.size();        G[from].push_back(m - 2);        G[to].push_back(m - 1);    }    int Maxflow(int s, int t) {        int flow = 0;        for (;;) {            memset(a, 0, sizeof(a));            queue<int> Q;            Q.push(s);            a[s] = INF;            while (!Q.empty()) {                int x = Q.front(); Q.pop();                for (int i = 0; i < G[x].size(); ++i) {                    Edge &e = edges[G[x][i]];                    if (!a[e.to] && e.cap > e.flow) {                        p[e.to] = G[x][i];                        a[e.to] = min(a[x], e.cap - e.flow);                        Q.push(e.to);                    }                }                if (a[t]) break;            }            if (!a[t]) break;            for (int u = t; u != s; u = edges[p[u]].from) {                edges[p[u]].flow += a[t];                edges[p[u] ^ 1].flow -= a[t];            }            flow += a[t];        }        return flow;    }};int main(int argc, char** argv) {#ifdef LOCAL    freopen("input.txt", "r", stdin);    freopen("output.txt", "w", stdout);#endif    int N, M; cin >> N >> M;    EdmondsKarp edk;    edk.init(N + N);    int u, v;    for (int i = 1; i <= M; ++i) {        cin >> u >> v;        edk.AddEdge(u, v + N, 1);    }    for (int i = 1; i <= N; ++i) {        edk.AddEdge(0, i, 1);        edk.AddEdge(N + i, N + N + 1, 1);    }    cout << N - edk.Maxflow(0, N + N + 1) << endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

网络流五·最大权闭合子图

题目连接

最大权闭合子图:目前就我的理解是用来建模求解一些有“收入”以及“支出”并且求最后最大的收益类问题的。建模方法如下:

1、添加源点 s 和汇点 t 。 
2、从源点 s 向 X 集合中每个点连一条容量为该点“收入”的有向边。 
3、从 Y 集合中每个点向汇点 t 连一条容量为该点“支出”的有向边。 
4、若 X 和 Y 集合中的点有依赖关系,则从 X 集合向 Y 集合每个关系添加一条容量为无限大的有向边。

最终的结果为所有收入之和 - 最小割。

const int maxn = 1005;const int INF = 0x7FFFFFFF;struct Edge {    int from, to, cap, flow;    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}};// 求最小割所用到的两个// 求最小割点的思路为在原来求最大流的残留网络上从 s 点开始 DFS,所有能遍历到的点都是 S 集合里面的,// 剩余没有遍历到的点就是 T 集合里的点。// bool used[maxn];// std::vector<int> rst;struct EdmondsKarp {    int n, m;    vector<Edge> edges;    vector<int> G[maxn];    int a[maxn];    int p[maxn];    void init(int n) {        for (int i = 0; i < n; ++i) G[i].clear();        edges.clear();    }    void AddEdge(int from, int to, int cap) {        edges.push_back(Edge(from, to, cap, 0));        edges.push_back(Edge(to, from, 0, 0));  // reverse edge        m = edges.size();        G[from].push_back(m - 2);        G[to].push_back(m - 1);    }    int Maxflow(int s, int t) {        int flow = 0;        for (;;) {            memset(a, 0, sizeof(a));            queue<int> Q;            Q.push(s);            a[s] = INF;            while (!Q.empty()) {                int x = Q.front(); Q.pop();                for (int i = 0; i < G[x].size(); ++i) {                    Edge &e = edges[G[x][i]];                    if (!a[e.to] && e.cap > e.flow) {                        p[e.to] = G[x][i];                        a[e.to] = min(a[x], e.cap - e.flow);                        Q.push(e.to);                    }                }                if (a[t]) break;            }            if (!a[t]) break;            for (int u = t; u != s; u = edges[p[u]].from) {                edges[p[u]].flow += a[t];                edges[p[u] ^ 1].flow -= a[t];            }               flow += a[t];        }        return flow;    }    bool CheckMaxMatch(int N, int M) {        for (int i = 1; i <= M; ++i) {            for (int j = 0; j < G[N + i].size(); ++j) {                Edge &e = edges[G[N + i][j]];                if (e.flow != e.cap && e.flow > 0) { return false; }            }                   }        return true;    }    /*     // 遍历求网络的最小割中的 S 集合点,结果储存在上面的 vector<int> rst 中;    void GetMinCutSetS(int s) {        rst.push_back(s); used[s] = true;        for (int i = 0; i < G[s].size(); ++i) {            Edge &e = edges[G[s][i]];            if (!used[e.to] && e.flow != e.cap) {                GetMinCutSetS(e.to);            }        }    }    */};int main(int argc, char** argv) {#ifdef LOCAL    freopen("input.txt", "r", stdin);    freopen("output.txt", "w", stdout);#endif    int N, M; cin >> N >> M;    int b[maxn], sum = 0;    EdmondsKarp ek;    ek.init(N + M + 2);    // 第i个数表示邀请编号为i的学生需要花费的活跃值b[i]    for (int i = 1; i <= M; ++i) cin >> b[i];    for (int i = 1; i <= N; ++i) {        int a, k, recvtmp; cin >> a >> k;        sum += a;        ek.AddEdge(0, i, a);        for (int j = 1; j <= k; ++j) {            cin >> recvtmp;            ek.AddEdge(i, recvtmp + N, INF);        }    }    for (int i = 1; i <= M; ++i) {        ek.AddEdge(i + N, N + M + 1, b[i]);    }    cout << sum - ek.Maxflow(0, N + M + 1) << endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
0
 
0