网络流刷题笔记

本文使用的是 Trovelrd 的文章里的例题,在此鸣谢。

P2764 最小路径覆盖问题

题目链接

网络流二十四题之一。

考虑对于图上的每个节点拆点,拆成入点和出点,所有入点和源点连边,所有出点和汇点连边,容量为 \(1\)

对于原图中的一条边 \((u,v)\),将 \(u\) 的入点和 \(v\) 的出点连边即可,容量为 \(1\)

这样建出来的图,必须满足每个入点只能有一个出点连,每个出点也只能被一个入点连,跑二分图最大匹配即可。

每个增广路对应两个能合并的节点,则最大流就是能合并的节点个数,本来最多要 \(n\) 条路径才能覆盖完,现在只有 \(n-\text{maxflow}\) 条,所以答案即为 \(n-\text{maxflow}\)

考虑输入方案,就是每条增广路对应着一条流量为 \(1\)\(u\to v+n\) 的边,此时 \(u\)\(v\) 就可以合并,若又有一条 \(v\to w+n\) 的边,则 \(v\) 也可以和 \(w\) 合并,则 \(u\to v\to w\) 就是原图上一条路径。

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 310, M = 1.5e4;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int n, m, nxt[N];
bool vis[N];
void solve()
{
    cin >> n >> m;
    int s = 0, t = n + n + 1;
    rep(i, 1, n)
    {
        G.addEdge(s, i, 1);
        G.addEdge(i + n, t, 1);
    }
    rep(i, 1, m)
    {
        int u, v;
        cin >> u >> v;
        G.addEdge(u, v + n, 1);
    }
    int res = G.maxflow(s, t);
    rep(i, 1, n)
    {
        for (int j = G.head[i]; j; j = G.nxt[j])
        {
            int v = G.to[j];
            if (!G.lim[j] && v > n && v <= n + n)
            {
                nxt[i] = v - n;
                vis[v - n] = 1;
            }
        }
    }
    rep(i, 1, n)
    {
        if (!vis[i])
        {
            vis[i] = 1;
            int u = i;
            while (u)
            {
                cout << u << " ";
                u = nxt[u];
            }
            cout << "\n";
        }
    }
    cout << n - res << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

P2765 魔术球问题

题目链接

网络流二十四题之一。

做完上一题,相信对这一题肯定有感觉了。没错,这道题只需要将和为完全平方数的两个数 \(x,y\)\((x<y)\),连一条 \(x\to y\) 的边即可。下面有两种做法。

二分答案

二分答案是最无脑的做法了,每次二分答案直接建图,竟然不会超时!(甚至不用吸氧)

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 310, M = 1.5e4;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int n, m, nxt[N];
bool vis[N];
void solve()
{
    cin >> n >> m;
    int s = 0, t = n + n + 1;
    rep(i, 1, n)
    {
        G.addEdge(s, i, 1);
        G.addEdge(i + n, t, 1);
    }
    rep(i, 1, m)
    {
        int u, v;
        cin >> u >> v;
        G.addEdge(u, v + n, 1);
    }
    int res = G.maxflow(s, t);
    rep(i, 1, n)
    {
        for (int j = G.head[i]; j; j = G.nxt[j])
        {
            int v = G.to[j];
            if (!G.lim[j] && v > n && v <= n + n)
            {
                nxt[i] = v - n;
                vis[v - n] = 1;
            }
        }
    }
    rep(i, 1, n)
    {
        if (!vis[i])
        {
            vis[i] = 1;
            int u = i;
            while (u)
            {
                cout << u << " ";
                u = nxt[u];
            }
            cout << "\n";
        }
    }
    cout << n - res << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

枚举答案

从小枚举答案的时候每次不把原来加的边删掉,而是直接加用有关当且枚举的答案的边,在上次跑完的残量网络上再跑最大流即可,感性理解一下是对的,注意每次最大流答案需要累加。

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 3210, M = 3e5 + 10;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void init()
    {
        memset(head, 0, sizeof(head));
        cnt = 1;
    }
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int n, nxt[N], s = 0, t = 1;
bool ok[N];
void add_new(int x)
{
    G.addEdge(s, (x << 1), 1);
    G.addEdge((x << 1 | 1), t, 1);
    for (int i = 1; i * i < x + x; i++)
    {
        if (i * i - x > 0)
        {
            G.addEdge(((i * i - x) << 1), (x << 1 | 1), 1);
        }
    }
}
void solve()
{
    cin >> n;
    int l = 0, sum = 0;
    while (1)
    {
        add_new(++l);
        int mf = G.maxflow(s, t);
        sum += mf;
        if (l - sum > n)
            break;
    }
    l--;
    cout << l << endl;
    for (int u = 2; u <= l + l; u += 2)
    {
        for (int i = G.head[u]; i; i = G.nxt[i])
        {
            int v = G.to[i];
            if (!G.lim[i] && (v & 1))
            {
                nxt[u >> 1] = (v >> 1);
                ok[(v >> 1)] = 1;
            }
        }
    }
    rep(i, 1, l)
    {
        if (!ok[i])
        {
            ok[i] = 1;
            int u = i;
            while (u)
            {
                cout << u << " ";
                u = nxt[u];
            }
            cout << endl;
        }
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

P2766 最长不下降子序列问题

题目链接

网络流二十四题之一。

这道题用到了网络流的一个经典思想:分层建模。在接下来的建图中,你会体会到分层建模的巧妙之处。

首先,第一问直接暴力 DP 求解即可,设 \(f_i\) 表示以 \(a_i\) 结尾的最长不降子序列的长度,\(m\) 为第一题的答案。第二、三问考虑使用网络流处理。

第二问中,考虑到每个点最多只能被使用一次的条件不好在图中表示,所以将一个点拆成入点和出点两个点,连容量为 \(1\) 的边。例如, \(u\) 被拆成 \(u\)\(u+n\) 两个点。

\(f_i\) 分层,具体来说:

  • \(f_i=1\),则 \(S\to i\) 连容量为 \(1\) 的边。
  • \(f_i=m\),则 \(i+n\to T\) 连容量为 \(1\) 的边。
  • \(i<j\)\(a_i\leq a_j\)\(f_j=f_i+1\)(这就是分层的思想),则 \(i+n\to j\) 连容量为 \(1\) 的边。

答案即为最大流。

借用一下 Troverld 大佬的图(图中拆点没有表现):

考虑第三问,相比第二问去掉了 \(1\)\(n\) 只能用 \(1\) 次这个条件,那只需要加入如下几条容量都是 \(+\infin\) 的边即可:

  • \(1\to 1+n\)
  • \(n\to n+n\)
  • \(S\to 1\)\(f_1\) 必然为 \(1\)
  • \(f_n=m\),则 \(n+n\to T\)

答案也是最大流。

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 1010, M = 250000;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void init()
    {
        memset(head, 0, sizeof(head));
        cnt = 1;
    }
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int n, a[510], dp[510];
void solve()
{
    cin >> n;
    if (n == 1)
    {
        cout << 1 << endl
             << 1 << endl
             << 1 << endl;
        return;
    }
    int mx = 0;
    rep(i, 1, n)
    {
        cin >> a[i];
        rep(j, 0, i - 1)
        {
            if (a[j] <= a[i])
            {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        mx = max(mx, dp[i]);
    }
    cout << mx << endl;
    int s = 0, t = n + n + 1;
    rep(i, 1, n)
    {
        G.addEdge(i, i + n, 1);
        if (dp[i] == 1)
        {
            G.addEdge(s, i, 1);
        }
        if (dp[i] == mx)
        {
            G.addEdge(i + n, t, 1);
        }
    }
    rep(i, 1, n)
    {
        rep(j, i + 1, n)
        {
            if (dp[j] == dp[i] + 1 && a[i] <= a[j])
            {
                G.addEdge(i + n, j, 1);
            }
        }
    }
    int res = G.maxflow(s, t);
    cout << res << endl;
    G.addEdge(1, 1 + n, INF);
    G.addEdge(n, n + n, INF);
    G.addEdge(s, 1, INF);
    if (dp[n] == mx)
        G.addEdge(n + n, t, INF);
    cout << res + G.maxflow(s, t) << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

P2774 方格取数问题

题目链接

网络流二十四题之一。

经典题,用到了一个方格图网络流建模的重要思想:黑白染色

对网格黑白染色,即 \((i+j)\) 为奇数的然黑色,为偶数的染白色。

则源点向黑点连黑点权值容量的边,黑点向相邻白点连容量为 \(+\infin\) 的边,白点向汇点连白点权值容量的边。

则答案为原来所有点权和减去最小割。

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 1e4 + 10, M = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void init()
    {
        memset(head, 0, sizeof(head));
        cnt = 1;
    }
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int n, m, a[110][110];
int getid(int x, int y)
{
    return (x - 1) * m + y;
}
void solve()
{
    cin >> n >> m;
    int s = 0, t = n * m + 1, sum = 0;
    rep(i, 1, n)
    {
        rep(j, 1, m)
        {
            cin >> a[i][j];
            sum += a[i][j];
        }
    }
    rep(i, 1, n)
    {
        rep(j, 1, m)
        {
            if ((i + j) & 1)
            {
                G.addEdge(s, getid(i, j), a[i][j]);
                rep(k, 0, 3)
                {
                    int x = i + dx[k], y = j + dy[k];
                    if (x >= 1 && x <= n && y >= 1 && y <= m)
                    {
                        G.addEdge(getid(i, j), getid(x, y), INF);
                    }
                }
            }
            else
            {
                G.addEdge(getid(i, j), t, a[i][j]);
            }
        }
    }
    cout << sum - G.maxflow(s, t) << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

P2805 [NOI2009] 植物大战僵尸

题目链接

先引入一个概念:闭合子图。

闭合子图就是说在一个有向图里,如果从一个子图中的所有点开始 DFS,到达的所有点也是这个子图里的,则称该子图为闭合子图。

显然,如果把原题中的保护关系(包括同一行的先后关系也算)反向建边(即被保护的点向保护它的点连边),则我们要求的就是一个 最大权闭合子图。因为如果有一个点能到达子图外的其他点,则这个点肯定不能被吃掉。

下面考虑如何求最大权闭合子图。用网络流解决这个问题,把所有正权点都连向源点,负权点都连向汇点,边权就是当前点权值的绝对值,然后保护关系反向连边,容量为 \(+\infin\)

则答案为所有正权点的权值和减掉最小割。

不是很会证明,但是可以感性理解一下(下面是瞎扯,可以略过):首先断掉的一定要么是和源点连边要么和汇点连边,如果断的是前者,则代表放弃一个正权点;如果断的是后者,则代表选择一个负权点(因为最后会拿正权点的权值减掉它,相当于贡献是负的)。建议自己画个图就能理解了。

这样问题就解决了,但是如果写出来会 WA,原因是如果出现环是不可能取到环上的点的,所以需要把环扔掉,即做一遍拓扑排序。这样就可以通过了。

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 600 + 10, M = 1e6 + 10;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void init()
    {
        memset(head, 0, sizeof(head));
        cnt = 1;
    }
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int n, m, a[22][33], deg[22][33];
queue<pii> q;
vector<pii> g[22][33];
int getid(int x, int y)
{
    return x * m + y + 1;
}
void solve()
{
    cin >> n >> m;
    int s = 0, t = n * m + 1;
    rep(i, 0, n - 1)
    {
        rep(j, 0, m - 1)
        {
            cin >> a[i][j];
            int k;
            cin >> k;
            while (k--)
            {
                int x, y;
                cin >> x >> y;
                G.addEdge(getid(x, y), getid(i, j), INF);
                g[i][j].push_back({x, y});
                deg[x][y]++;
            }
        }
    }
    rep(i, 0, n - 1)
    {
        rep(j, 0, m - 2)
        {
            G.addEdge(getid(i, j), getid(i, j + 1), INF);
            g[i][j + 1].push_back({i, j});
            deg[i][j]++;
        }
    }
    rep(i, 0, n - 1)
    {
        rep(j, 0, m - 1)
        {
            if (!deg[i][j])
            {
                q.push({i, j});
            }
        }
    }
    int sum = 0;
    while (!q.empty())
    {
        int x = q.front().first, y = q.front().second;
        q.pop();
        if (a[x][y] >= 0)
        {
            G.addEdge(s, getid(x, y), a[x][y]);
            sum += a[x][y];
        }
        else
        {
            G.addEdge(getid(x, y), t, -a[x][y]);
        }
        for (auto [tx, ty] : g[x][y])
        {
            deg[tx][ty]--;
            if (!deg[tx][ty])
            {
                q.push({tx, ty});
            }
        }
    }
    cout << sum - G.maxflow(s, t) << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

题目链接

网络流二十四题之一。

简单贪心题用费用流,哦耶!

好像是第一道费用流耶,比较基础。设 \(A\) 表示 \(a_1,a_2,\cdots,a_n\) 的平均数,则建图方式如下:

  • \(a_i\geq A\),则 \(s\to i\) 连一条容量为 \(a_i-A\),费用为 \(0\) 的边。
  • \(a_i<A\),则 \(i\to t\) 连一条容量为 \(A-a_i\),费用为 \(0\) 的边。
  • \(i\to i+1\)\(i\to i-1\) 连容量为 \(+\infin\),费用为 \(0\) 的边。

跑费用流,费用即为答案。(最大流限制了平均,费用流保证了最小)

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 110, M = 810;
const int INF = 0x3f3f3f3f;
struct MCMF
{
    int head[N], nxt[M], to[M], lim[M], cst[M], cnt = 1;
    void addEdge(int u, int v, int w, int c)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w, cst[cnt] = c;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0, cst[cnt] = -c;
    }
    int fr[N], fl[N], dis[N];
    bool vis[N];
    pair<int, int> mcmf(int s, int t)
    {
        int flow = 0, cost = 0;
        while (1)
        {
            memset(dis, 0x3f, sizeof(dis));
            memset(vis, 0, sizeof(vis));
            queue<int> q;
            q.push(s);
            fl[s] = INF;
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                vis[u] = 0;
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i], d = dis[u] + cst[i];
                    if (lim[i] && dis[v] > d)
                    {
                        dis[v] = d;
                        fr[v] = i;
                        fl[v] = min(lim[i], fl[u]);
                        if (!vis[v])
                        {
                            vis[v] = 1;
                            q.push(v);
                        }
                    }
                }
            }
            if (dis[t] > 1e9)
                return {flow, cost};
            flow += fl[t], cost += dis[t] * fl[t];
            for (int u = t; u != s; u = to[fr[u] ^ 1])
            {
                lim[fr[u]] -= fl[t];
                lim[fr[u] ^ 1] += fl[t];
            }
        }
    }
} G;
int n, a[N];
void solve()
{
    cin >> n;
    int s = 0, t = n + 1;
    int sum = 0;
    rep(i, 1, n)
    {
        cin >> a[i];
        sum += a[i];
    }
    int ave = sum / n;
    rep(i, 1, n)
    {
        if (a[i] >= ave)
            G.addEdge(s, i, a[i] - ave, 0);
        else
            G.addEdge(i, t, ave - a[i], 0);
        G.addEdge(i, i % n + 1, INF, 1);
        G.addEdge(i, (i - 2 + n) % n + 1, INF, 1);
    }
    cout << G.mcmf(s, t).second << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}

P3254 圆桌问题

直接暴力建图即可。

源点向每个单位连容量为 \(r_i\) 的边,单位向每个桌子连容量为 \(1\) 的边,桌子向汇点连容量为 \(c_i\) 的边。

直接跑最大流即可。

点击查看代码
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
// #define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define ALL(a) (a).begin(), (a).end()
#define endl "\n"
int max(int x, int y) { return x > y ? x : y; }
int min(int x, int y) { return x < y ? x : y; }
template <typename T>
void chmax(T &x, int y)
{
    if (y > x)
        x = y;
}
template <typename T>
void chmin(T &x, int y)
{
    if (y < x)
        x = y;
}
using namespace std;
void setIO(string name)
{
    freopen((name + ".in").c_str(), "r", stdin);
    freopen((name + ".out").c_str(), "w", stdout);
}
const int N = 450, M = 1e5;
const int INF = 0x3f3f3f3f;
struct MaxFlow
{
    int head[N], nxt[M], to[M], cnt = 1;
    int lim[M];
    void init()
    {
        memset(head, 0, sizeof(head));
        cnt = 1;
    }
    void addEdge(int u, int v, int w)
    {
        nxt[++cnt] = head[u], head[u] = cnt, to[cnt] = v, lim[cnt] = w;
        nxt[++cnt] = head[v], head[v] = cnt, to[cnt] = u, lim[cnt] = 0;
    };
    int T, cur[N], dis[N];
    int dfs(int u, int res)
    {
        if (u == T)
            return res;
        int flow = 0;
        for (int i = cur[u]; i && res; i = nxt[i])
        {
            cur[u] = i;
            int v = to[i];
            int c = min(lim[i], res);
            if (c && dis[v] == dis[u] + 1)
            {
                int f = dfs(v, c);
                res -= f;
                flow += f;
                lim[i] -= f;
                lim[i ^ 1] += f;
            }
        }
        return flow;
    }
    int maxflow(int s, int t)
    {
        T = t;
        int flow = 0;
        while (1)
        {
            queue<int> q;
            memset(dis, -1, sizeof(dis));
            memcpy(cur, head, sizeof(head));
            q.push(s);
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i; i = nxt[i])
                {
                    int v = to[i];
                    if (dis[v] == -1 && lim[i])
                    {
                        dis[v] = dis[u] + 1;
                        q.push(v);
                    }
                }
            }
            if (dis[t] == -1)
                return flow;
            flow += dfs(s, INF);
        }
    }
} G;
int m, n, r[160], c[280];
void solve()
{
    cin >> m >> n;
    int s = 0, t = m + n + 1;
    int sum = 0;
    rep(i, 1, m)
    {
        cin >> r[i];
        sum += r[i];
        G.addEdge(s, i, r[i]);
    }
    rep(i, 1, n)
    {
        cin >> c[i];
        G.addEdge(i + m, t, c[i]);
    }
    rep(i, 1, m)
    {
        rep(j, 1, n)
        {
            G.addEdge(i, j + m, 1);
        }
    }
    int mf = G.maxflow(s, t);
    if (mf < sum)
    {
        cout << "0" << endl;
        return;
    }
    cout << "1" << endl;
    rep(i, 1, m)
    {
        for (int j = G.head[i]; j; j = G.nxt[j])
        {
            if (!G.lim[j])
            {
                cout << G.to[j] - m << " ";
            }
        }
        cout << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}
posted @ 2023-11-01 18:14  Jerry_Jiang  阅读(26)  评论(0编辑  收藏  举报