图论建模技巧搜集

一些经典题目

找可达路径

  UVa11604 General Sultan

最小割建模

  UVa1515 Pool construction

  洛谷 P3227 [HNOI2013] 切糕
洛谷题解

/**
 * 洛谷 P3227 [HNOI2013] 切糕
 */
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

#define INF 1010
#define N 74088
struct edge {int u, v, cap, flow;}; vector<edge> e; vector<int> g[N];
int q[N], p[N], d[N], cur[N], num[N], P, Q, R, D; bool vis[N];

void add_edge(int u, int v, int cap) {
    g[u].push_back(e.size()); e.push_back({u, v, cap, 0});
    g[v].push_back(e.size()); e.push_back({v, u, 0, 0});
}

bool bfs(int s, int t) {
    memset(vis, 0, sizeof(vis)); memset(d, 0, sizeof(d)); q[0] = t; d[t] = 0; vis[t] = true;
    int head = 0, tail = 1;
    while (head < tail) {
        int v = q[head++];
        for (int i=0; i<g[v].size(); ++i) {
            const edge& ee = e[g[v][i]^1];
            if (!vis[ee.u] && ee.cap > ee.flow) vis[ee.u] = true, d[ee.u] = d[v] + 1, q[tail++] = ee.u;
        }
    }
    return vis[s];
}

int solve() {
    int s = 0, t = P*Q*R + 1, cc = 0; e.clear();
    for (int i=0; i<N; ++i) g[i].clear();
    for (int z=0; z<R; ++z) for (int x=1; x<=P; ++x) for (int y=1; y<=Q; ++y) {
        int u = P*Q*z + (x-1)*Q + y, v; cin >> v;
        add_edge(z ? u-P*Q : s, u, v);
        if (z < D) continue;
        if (x > 1) add_edge(u, u - P*Q*D - Q, INF);
        if (x < P) add_edge(u, u - P*Q*D + Q, INF);
        if (y > 1) add_edge(u, u - P*Q*D - 1, INF);
        if (y < Q) add_edge(u, u - P*Q*D + 1, INF);
    }
    for (int x=1; x<=P; ++x) for (int y=1; y<=Q; ++y) add_edge(P*Q*(R-1) + (x-1)*Q + y, t, INF);
    if (!bfs(s, t)) return 0;
    memset(num, 0, sizeof(num)); memset(cur, 0, sizeof(cur));
    for (int i=0; i<=t; ++i) ++num[d[i]];
    int u = s;
    while (d[s] <= t) {
        if (u == t) {
            int a = INF;
            for (int v=t; v!=s; v = e[p[v]].u) a = min(a, e[p[v]].cap - e[p[v]].flow);
            for (int v=t; v!=s; v = e[p[v]].u) e[p[v]].flow += a, e[p[v]^1].flow -= a;
            cc += a; u = s;
        }
        int ok = 0;
        for (int i=cur[u]; i<g[u].size(); ++i) {
            const edge& ee = e[g[u][i]];
            if (ee.cap > ee.flow && d[u] == d[ee.v] + 1) {
                ok = 1; p[ee.v] = g[u][i]; cur[u] = i; u = ee.v;
                break;
            }
        }
        if (!ok) {
            int m = t;
            for (int i=0; i<g[u].size(); ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow) m = min(m, d[ee.v]);
            }
            if (--num[d[u]] == 0) break;
            ++num[d[u] = m + 1]; cur[u] = 0;
            if (u != s) u = e[p[u]].u;
        }
    }
    return cc;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> P >> Q >> R >> D) cout << solve() << endl;
    return 0;
}

  UVa1660/LA3031 Cable TV Network

费用流建模

  P1251 餐巾计划问题
  本题建图的拆点方式和多数网络流的拆点方式不同,以往拆点后点对 i , i ′ i,i^′ i,i之间会连边从而满足流量限制。而此题拆点后 i , i ′ i,i^′ i,i之间是割裂的,此题是将点 i i i当做当天用后的脏毛巾,点 i ′ i^′ i则当作当天需要的干净毛巾。当天干净毛巾需求量已知并且获取途径已知(之前某天的脏毛巾快洗/慢洗,或者直接购买新毛巾),当天产生的脏毛巾总量已知并且转移途径已知(积压到下一天、快洗/慢洗变成之后某天的干净毛巾)。根据这些约束建图求最小费用即可。
  建图方式:源点 S S S向每个结点 i i i连边,容量为 r i r_i ri,费用为0;每个结点 i ′ i^′ i向汇点 T T T连边,容量为 r i r_i ri,费用为0;源点 S S S向每个结点 i ′ i^′ i连边,容量为 i n f inf inf(也可以是 r i r_i ri),费用为 p p p;每个结点 i i i ( i + m ) ′ (i+m)^′ (i+m)连边,容量为 i n f inf inf(也可以是 r i + m r_{i+m} ri+m),费用为 f f f;每个结点 i i i ( i + n ) ′ (i+n)^′ (i+n)连边,容量为 i n f inf inf(也可以是 r i + n r_{i+n} ri+n),费用为 s s s;每个结点 i i i i + 1 i+1 i+1连边,容量为 i n f inf inf,费用为0。

#include <iostream>
#include <cstring>
using namespace std;

#define INF 30000000000
#define N 4004
struct edge {int u, v, cost; long long cap, flow;} e[6*N];
int g[N][N], q[6*N*N], d[N], p[N], cnt[N], r[N], c, n; long long a[N]; bool vis[N];

void add_edge(int u, int v, long long cap, int cc) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}

long long solve() {
    memset(cnt, 0, sizeof(cnt));
    long long cc = 0; int s = 0, t = 2*n+1;
    for (int i=1; i<=n; ++i) cin >> r[i];
    int _p, _m, _f, _n, _s; cin >> _p >> _m >> _f >> _n >> _s;
    for (int i=1; i<=n; ++i) {
        add_edge(s, i, r[i], 0); add_edge(i+n, t, r[i], 0); add_edge(0, i+n, INF, _p);
        if (i < n) add_edge(i, i+1, INF, 0);
        if (_m+i <= n) add_edge(i, _m+i+n, INF, _f);
        if (_n+i <= n) add_edge(i, _n+i+n, INF, _s);
    }
    while (true) {
        memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
        d[s] = 0; q[0] = s; a[s] = INF;
        int head = 0, tail = 1;
        while (head < tail) {
            int u = q[head++]; vis[u] = false;
            for (int i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
                    d[ee.v] = d[u]+ee.cost;
                    p[ee.v] = g[u][i];
                    a[ee.v] = min(a[u], ee.cap-ee.flow);
                    if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
                }
            }
        }
        if (d[t] >= 20000) return cc;
        cc += d[t] * a[t];
        for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
    }
    return cc;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n) cout << solve() << endl;
    return 0;
}

  洛谷 P3159 [CQOI2012] 交换棋子

#include <iostream>
#include <cstring>
using namespace std;

#define INF 20000000
#define N 802
struct edge {int u, v, cap, flow, cost;} e[12*N]; char s1[22][22], s2[22][22], s3[22][22];
int g[N][N>>1], q[12*N*N], a[N], d[N], p[N], cnt[N], c, m, n; bool vis[N];

void add_edge(int u, int v, int cap, int cc) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}

int solve() {
    for (int i=1; i<=n; ++i) cin >> s1[i]+1;
    for (int i=1; i<=n; ++i) cin >> s2[i]+1;
    for (int i=1; i<=n; ++i) cin >> s3[i]+1;
    memset(cnt, c = 0, sizeof(cnt));
    int s = 0, t = 2*m*n+1, f = 0, cc = 0;
    for (int i=1, x=1; i<=n; ++i) for (int j=1; j<=m; ++j, ++x) {
        int k = s3[i][j] - '0', y = x+m*n;
        if (s1[i][j] == '1') {
            if (s2[i][j] == '0') add_edge(s, x, 1, 0), ++f;
        } else if (s2[i][j] == '1') add_edge(y, t, 1, 0), --f;
        add_edge(x, y, k&1 && s1[i][j] != s2[i][j] ? (k+1)>>1 : k>>1, 0);
        if (i > 1) {
            add_edge(y, x-m, INF, 1);
            if (j > 1) add_edge(y, x-m-1, INF, 1);
            if (j < m) add_edge(y, x-m+1, INF, 1);
        }
        if (j > 1) add_edge(y, x-1, INF, 1);
        if (j < m) add_edge(y, x+1, INF, 1);
        if (i < n) {
            add_edge(y, x+m, INF, 1);
            if (j > 1) add_edge(y, x+m-1, INF, 1);
            if (j < m) add_edge(y, x+m+1, INF, 1);
        }
    }
    if (f) return -1;
    while (true) {
        memset(d, 0x7f, sizeof(d)); memset(vis, 0, sizeof(vis));
        d[s] = 0; q[0] = s; a[s] = INF;
        int head = 0, tail = 1;
        while (head < tail) {
            short u = q[head++]; vis[u] = false;
            for (short i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
                    d[ee.v] = d[u]+ee.cost;
                    p[ee.v] = g[u][i];
                    a[ee.v] = min(a[u], ee.cap-ee.flow);
                    if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
                }
            }
        }
        if (d[t] >= INF) return cc;
        cc += d[t] * a[t];
        for (short u=t; u!=s; u=e[p[u]].u) {
            e[p[u]].flow += a[t];
            e[p[u]^1].flow -= a[t];
        }
    }
    return cc;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> m) cout << solve() << endl;
    return 0;
}

  洛谷 P4003 [2017国家集训队测试] 无限之环
  此题仅分析有无解的话,就是最大流问题,建图方式也很自然能想到。要求最优解则变成了费用流问题了,这个时候费用安排在哪就是个难点了,按水管的不同形状依次做分析还是能找到突破口。
水管分类
  可以把水管分成上图四类:第一行不能转(题目特地交待的只能转非直线型水管)或者不需要转,不需要安排费用;第二行只有一个口,根据旋转情况直接知道费用该怎么安排;第三行,转90度的话可看成只有一个口位置变了,转180度的话两个口转到对面了,费用也知道该怎么安排;第四行和第二行类似。
  题目暗搓搓的交代只能转非直线型水管,就是为了使得费用能安排下去,如果直线型水管也能转就不行了。

#include <iostream>
#include <cstring>
using namespace std;

#define T u+d
#define R u+2*d
#define B u+3*d
#define L u+4*d
#define N 10002
struct edge {int u, v, cap, flow, cost;} e[N<<2];
int g[N][2002], q[N*N<<2], a[N], d[N], p[N], cnt[N], c, m, n; bool vis[N];

void add_edge(int u, int v, int cap, int cc) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}

int solve() {
    int s = 0, t = 5*m*n + 1, b = 0, f = 0, cc = 0; memset(cnt, c = 0, sizeof(cnt));
    for (int i=1, u=1, d=m*n; i<=n; ++i) for (int j=1; j<=m; ++j, ++u) {
        int x; cin >> x;
        if (!x) continue;
        if ((i+j) & 1) {
            add_edge(s, u, 4, 0);
            if (x == 1) ++f, add_edge(u, T, 1, 0), add_edge(T, R, 1, 1), add_edge(T, L, 1, 1), add_edge(T, B, 1, 2);
            if (x == 2) ++f, add_edge(u, R, 1, 0), add_edge(R, T, 1, 1), add_edge(R, B, 1, 1), add_edge(R, L, 1, 2);
            if (x == 4) ++f, add_edge(u, B, 1, 0), add_edge(B, L, 1, 1), add_edge(B, R, 1, 1), add_edge(B, T, 1, 2);
            if (x == 8) ++f, add_edge(u, L, 1, 0), add_edge(L, T, 1, 1), add_edge(L, B, 1, 1), add_edge(L, R, 1, 2);
            if (x == 3) f+=2, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(T, B, 1, 1), add_edge(R, L, 1, 1);
            if (x == 6) f+=2, add_edge(u, B, 1, 0), add_edge(u, R, 1, 0), add_edge(B, T, 1, 1), add_edge(R, L, 1, 1);
            if (x == 9) f+=2, add_edge(u, T, 1, 0), add_edge(u, L, 1, 0), add_edge(T, B, 1, 1), add_edge(L, R, 1, 1);
            if (x == 12) f+=2, add_edge(u, B, 1, 0), add_edge(u, L, 1, 0), add_edge(B, T, 1, 1), add_edge(L, R, 1, 1);
            if (x == 7) f+=3, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(u, B, 1, 0), add_edge(T, L, 1, 1), add_edge(B, L, 1, 1), add_edge(R, L, 1, 2);
            if (x == 11) f+=3, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(u, L, 1, 0), add_edge(R, B, 1, 1), add_edge(L, B, 1, 1), add_edge(T, B, 1, 2);
            if (x == 13) f+=3, add_edge(u, T, 1, 0), add_edge(u, B, 1, 0), add_edge(u, L, 1, 0), add_edge(T, R, 1, 1), add_edge(B, R, 1, 1), add_edge(L, R, 1, 2);
            if (x == 14) f+=3, add_edge(u, R, 1, 0), add_edge(u, B, 1, 0), add_edge(u, L, 1, 0), add_edge(R, T, 1, 1), add_edge(L, T, 1, 1), add_edge(B, T, 1, 2);
            if (x == 5) f+=2, add_edge(u, T, 1, 0), add_edge(u, B, 1, 0);
            if (x == 10) f+=2, add_edge(u, R, 1, 0), add_edge(u, L, 1, 0);
            if (x == 15) f+=4, add_edge(u, T, 1, 0), add_edge(u, R, 1, 0), add_edge(u, B, 1, 0), add_edge(u, L, 1, 0);
            if (i > 1) add_edge(T, B-m, 1, 0);
            if (j > 1) add_edge(L, R-1, 1, 0);
            if (i < n) add_edge(B, T+m, 1, 0);
            if (j < m) add_edge(R, L+1, 1, 0);
        } else {
            add_edge(u, t, 4, 0);
            if (x == 1) ++b, add_edge(T, u, 1, 0), add_edge(R, T, 1, 1), add_edge(L, T, 1, 1), add_edge(B, T, 1, 2);
            if (x == 2) ++b, add_edge(R, u, 1, 0), add_edge(T, R, 1, 1), add_edge(B, R, 1, 1), add_edge(L, R, 1, 2);
            if (x == 4) ++b, add_edge(B, u, 1, 0), add_edge(L, B, 1, 1), add_edge(R, B, 1, 1), add_edge(T, B, 1, 2);
            if (x == 8) ++b, add_edge(L, u, 1, 0), add_edge(T, L, 1, 1), add_edge(B, L, 1, 1), add_edge(R, L, 1, 2);
            if (x == 3) b+=2, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(B, T, 1, 1), add_edge(L, R, 1, 1);
            if (x == 6) b+=2, add_edge(B, u, 1, 0), add_edge(R, u, 1, 0), add_edge(T, B, 1, 1), add_edge(L, R, 1, 1);
            if (x == 9) b+=2, add_edge(T, u, 1, 0), add_edge(L, u, 1, 0), add_edge(B, T, 1, 1), add_edge(R, L, 1, 1);
            if (x == 12) b+=2, add_edge(B, u, 1, 0), add_edge(L, u, 1, 0), add_edge(T, B, 1, 1), add_edge(R, L, 1, 1);
            if (x == 7) b+=3, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, T, 1, 1), add_edge(L, B, 1, 1), add_edge(L, R, 1, 2);
            if (x == 11) b+=3, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(L, u, 1, 0), add_edge(B, R, 1, 1), add_edge(B, L, 1, 1), add_edge(B, T, 1, 2);
            if (x == 13) b+=3, add_edge(T, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, u, 1, 0), add_edge(R, T, 1, 1), add_edge(R, B, 1, 1), add_edge(R, L, 1, 2);
            if (x == 14) b+=3, add_edge(R, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, u, 1, 0), add_edge(T, R, 1, 1), add_edge(T, L, 1, 1), add_edge(T, B, 1, 2);
            if (x == 5) b+=2, add_edge(T, u, 1, 0), add_edge(B, u, 1, 0);
            if (x == 10) b+=2, add_edge(R, u, 1, 0), add_edge(L, u, 1, 0);
            if (x == 15) b+=4, add_edge(T, u, 1, 0), add_edge(R, u, 1, 0), add_edge(B, u, 1, 0), add_edge(L, u, 1, 0);
        }
    }
    if (b != f) return -1;
    f = 0;
    while (true) {
        memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
        d[s] = 0; q[0] = s; a[s] = N;
        int head = 0, tail = 1;
        while (head < tail) {
            short u = q[head++]; vis[u] = false;
            for (short i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
                    d[ee.v] = d[u]+ee.cost;
                    p[ee.v] = g[u][i];
                    a[ee.v] = min(a[u], ee.cap-ee.flow);
                    if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
                }
            }
        }
        if (d[t] >= N) break; 
        f += a[t]; cc += d[t] * a[t];
        for (short u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
    }
    return f == b ? cc : -1;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> m) cout << solve() << endl;
    return 0;
}

一些可以转化为二分图最大权匹配的建模题

  UVa1006/LA2238 Fixed Partition Memory Management

  洛谷 P2053 [SCOI2007] 修车

跑费用流的过程中动态加点连边

  洛谷P2050 [NOI2012] 美食节
  这道题目是洛谷P2053 [SCOI2007] 修车的加强版,数据量变大 ( n ≤ 40 , m ≤ 100 , P ≤ 800 , t i , j ≤ 1000 ,其中 P = ∑ p i ) (n≤40,m≤100,P≤800,t_{i,j}≤1000,其中P=∑p_i) (n40m100P800ti,j1000,其中P=pi),如果继续用KM算法求二分图最大权匹配的话,点数的规模是 𝑂(𝑚𝑃 + 𝑃),边数的规模是 𝑂(𝑃𝑚𝑃),会超时。
  如果直接用费用流求解,则点数的规模是𝑂(𝑚𝑃 + 𝑛),边数的规模是 𝑂(𝑛𝑚𝑃),也会超时。要想办法去掉冗余的点和边(每个厨师不会都要做𝑃道菜)。由于费用流算法每次只能找出一条增广路,所以可以先不连不需要的边。一开始,把所有厨师做倒数第1道菜与所有菜连好,然后找一条增广路,表示第j个厨师做倒数第1道菜,然后继续添加点(第j个厨师做倒数第2道菜),与汇点和所有菜连边,以此类推。这样做点数的规模是𝑂(𝑚+𝑛+𝑃),边数的规模是 𝑂(𝑛𝑃)。

#include <iostream>
#include <cstring>
using namespace std;

#define INF 900000
#define N 42
#define M 102
#define P 1002
struct edge {int u, v, cap, flow, cost, c;} e[N*P<<1];
int g[P][P], x[N][M], y[M], q[N*P*P<<1], a[P], d[P], p[P], cnt[P], c, m, n; bool vis[P];

void add_edge(int u, int v, int cap, int cc) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}

int solve() {
    memset(cnt, c = 0, sizeof(cnt));
    int s = 0, t = n+1, b = t, f, cc = 0;
    for (int i=1, c; i<=n; ++i) cin >> c, add_edge(0, i, c, 0);
    for (int i=1; i<=n; ++i) for (int j=1; j<=m; ++j)
        cin >> x[i][j], add_edge(i, b+j, 1, x[i][j]);
    for (int i=1; i<=m; ++i) y[i] = 1, e[c].c = i, add_edge(++b, t, 1, 0);
    while (true) {
        memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis));
        d[s] = 0; q[0] = s; a[s] = INF;
        int head = 0, tail = 1;
        while (head < tail) {
            short u = q[head++]; vis[u] = false;
            for (short i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
                    d[ee.v] = d[u]+ee.cost;
                    p[ee.v] = g[u][i];
                    a[ee.v] = min(a[u], ee.cap-ee.flow);
                    if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
                }
            }
        }
        if (d[t] >= INF) return cc;
        cc += d[t];
        for (short u=t; u!=s; u=e[p[u]].u) {
            e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
            if (e[p[u]].v == t && e[p[u]].flow) f = e[p[u]].c;
        }
        ++y[f]; e[c].c = f; add_edge(++b, t, 1, 0);
        for (int i=1; i<=n; ++i) add_edge(i, b, 1, y[f]*x[i][f]);
    }
    return cc;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> m) cout << solve() << endl;
    return 0;
}

知识点

平面图最小割=对偶图最短路

  UVa1376 Animal Run

上下界网络流

  上下界网络流,包括4种类型:无源无汇有容量上下界网络的可行流、有源有汇有容量上下界网络的可行流、有源有汇有容量上下界网络的最大流、有源有汇有容量上下界网络的最小流。

二分图

  二分图匹配灵活运用

区间k覆盖问题

  将区间两端点加入数组,排序后去重离散化,设不同坐标数量为 m m m,以0号(最小坐标)作为源点,新增汇点 t = m t=m t=m。建图跑费用流: i → i + 1 i\rightarrow i+1 ii+1,流量为 k k k费用为0;对区间 [ i , j ) [i,j) [i,j)连边 i → j i\rightarrow j ij,流量为1费用为负边权 − w -w w
  注意上面的建图方法是针对开区间(全开或者半开半闭),如果数据存在闭区间,则需要将每个点一拆为二。
  P3358 最长k可重区间集问题

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

#define N 1002
struct edge {int u, v, cap, flow, cost;} e[3*N];
int g[N][N>>1], q[3*N*N], a[N], d[N], p[N], cnt[N], l[N], r[N], x[N], c, m, n, k; bool vis[N];

void add_edge(int u, int v, int cap, int cc) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}

int solve() {
    for (int i=m=0; i<n; ++i) cin >> l[i] >> r[i], x[m++] = l[i], x[m++] = r[i];
    sort(x, x+m); m = unique(x, x+m) - x; memset(cnt, c = 0, sizeof(cnt));
    int s = 0, t = m, cc = 0;
    for (int i=0; i<m; ++i) add_edge(i, i+1, k, 0);
    for (int i=0; i<n; ++i) add_edge(lower_bound(x, x+m, l[i]) - x, lower_bound(x, x+m, r[i]) - x, 1, l[i] - r[i]);
    while (true) {
        memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis)); d[s] = 0; q[0] = s; a[s] = k;
        int head = 0, tail = 1;
        while (head < tail) {
            int u = q[head++]; vis[u] = false;
            for (int i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
                    d[ee.v] = d[u]+ee.cost;
                    p[ee.v] = g[u][i];
                    a[ee.v] = min(a[u], ee.cap-ee.flow);
                    if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
                }
            }
        }
        if (d[t] >= 0) break;
        cc -= d[t] * a[t];
        for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
    }
    return cc;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> k) cout << solve() << endl;
    return 0;
}

  P3357 最长k可重线段集问题,这个题就是存在闭区间(与 x x x轴垂直的线段),需要拆点。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

#define N 2002
struct edge {int u, v, cap, flow; long long cost;} e[3*N]; long long d[N];
int g[N][N>>1], q[3*N*N], a[N], p[N], cnt[N], xl[N], yl[N], xr[N], yr[N], x[N], c, m, n, k; bool vis[N];

void add_edge(int u, int v, int cap, long long cc) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; e[c].cost = cc; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; e[c].cost = -cc; g[v][cnt[v]++] = c++;
}

int solve() {
    for (int i=m=0; i<n; ++i) cin >> xl[i] >> yl[i] >> xr[i] >> yr[i], x[m++] = xl[i], x[m++] = xr[i];
    sort(x, x+m); m = unique(x, x+m) - x; memset(cnt, c = 0, sizeof(cnt));
    int s = 0, t = 2*m; long long cc = 0;
    for (int i=0; i<m; ++i) add_edge(i, i+m, k, 0), add_edge(i+m, i+1==m ? t : i+1, k, 0);
    for (int i=0; i<n; ++i) {
        double dx = xl[i]-double(xr[i]), dy = yl[i]-double(yr[i]);
        int l = lower_bound(x, x+m, xl[i]) - x, r = lower_bound(x, x+m, xr[i]) - x;
        if (l == r) {
            add_edge(l, m+l, 1, -sqrt(dx*dx + dy*dy));
            continue;
        }
        if (l > r) l = l+r, r = l-r, l = l-r;
        add_edge(m+l, r, 1, -sqrt(dx*dx + dy*dy));
    }
    while (true) {
        memset(d, 1, sizeof(d)); memset(vis, 0, sizeof(vis)); d[s] = 0; q[0] = s; a[s] = k;
        int head = 0, tail = 1;
        while (head < tail) {
            int u = q[head++]; vis[u] = false;
            for (int i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow && d[ee.v] > d[u]+ee.cost) {
                    d[ee.v] = d[u]+ee.cost;
                    p[ee.v] = g[u][i];
                    a[ee.v] = min(a[u], ee.cap-ee.flow);
                    if (!vis[ee.v]) vis[q[tail++] = ee.v] = true;
                }
            }
        }
        if (d[t] >= 0) break;
        cc -= d[t] * a[t];
        for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
    }
    return cc;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> k) cout << solve() << endl;
    return 0;
}

混合图的欧拉回路

  UVa10735 Euler Circuit
  本题有洛谷 P3159 [CQOI2012] 交换棋子的影子,不过比洛谷 P3159 [CQOI2012] 交换棋子要简单一点。
  诡异的是,我写ISAP跑最大流就会WA,换成朴素的Edmonds–Karp算法跑最大流就就AC。

#include <iostream>
#include <cstring>
using namespace std;

#define M 600
#define N 102
struct edge {int u, v, cap, flow;} e[M<<1];
int g[N][M], q[M*N<<1], p[M], a[N], d[N], cnt[N], u[M], v[M], h[M], c, m, n; bool vis[M];

void add_edge(int u, int v, int cap) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; g[v][cnt[v]++] = c++;
}

void euler(int x) {
    for (int i=0, j; i<cnt[x]; ++i) if (!vis[j = g[x][i]]) vis[j] = true, euler(u[j]+v[j]-x), p[n++] = x;
}

bool solve() {
    cin >> n >> m; memset(cnt, c = 0, sizeof(cnt)); memset(d, 0, sizeof(d));
    for (int i=0; i<m; ++i) {
        char x; cin >> u[i] >> v[i] >> x; ++d[u[i]]; --d[v[i]]; h[i] = -1;
        if (x == 'U') h[i] = c, add_edge(u[i], v[i], 1);
    }
    int s = 0, t = n+1, f = 0;
    for (int i=1; i<=n; ++i) {
        if (d[i] & 1) return false;
        if (d[i] > 0) add_edge(s, i, d[i]>>1), f += d[i]>>1;
        if (d[i] < 0) add_edge(i, t, -d[i]>>1);
    }
    if (f) {
        while (true) {
            memset(a, 0, sizeof(a)); a[s] = m; q[0] = s;
            int head = 0, tail = 1;
            while (head < tail) {
                int u = q[head++];
                for (int i=0; i<cnt[u]; ++i) {
                    const edge& ee = e[g[u][i]];
                    if (!a[ee.v] && ee.cap > ee.flow) {
                        p[ee.v] = g[u][i];
                        a[ee.v] = min(a[u], ee.cap-ee.flow);
                        q[tail++] = ee.v;
                    }
                }
                if (a[t]) break;
            }
            if (!a[t]) break;
            f -= a[t];
            for (int u=t; u!=s; u=e[p[u]].u) e[p[u]].flow += a[t], e[p[u]^1].flow -= a[t];
        }
    }
    if (f) return false;
    memset(cnt, n = 0, sizeof(cnt));
    for (int i=0; i<m; ++i) {
        vis[i] = false; h[i] >= 0 && e[h[i]].flow ? g[v[i]][cnt[v[i]]++] = i : g[u[i]][cnt[u[i]]++] = i;
    }
    euler(1);
    while (n--) cout << p[n] << ' ';
    cout << 1 << endl;
    return true;
}

int main() {
    int t; cin >> t;
    while (t--) {
        if (!solve()) cout << "No euler circuit exist" << endl;
        if (t) cout << endl;
    }
    return 0;
}

最大权闭合子图

  洛谷P2762 太空飞行计划问题
  一些概念:对于一个有向图G,存在点集合V,任取点u属于V,u的所有出边的另一点也属于V,则为闭合子图。当每个点有一个权值w(有正有负),点权和最大的闭合子图为最大权闭合子图。
  建图求出最大流 f l o w flow flow后,记正权和为 s u m sum sum,则最大权和为 s u m − f l o w sum-flow sumflow
  说一下求最大权闭合子图点集V的方法:根据所采用的最大流算法求出S点集和T点集,S点集去掉源点就是最大权闭合子图点集V。

#include <iostream>
#include <cstring>
using namespace std;

#define INF 0x7f7f7f7f
#define N 102
struct edge {int u, v, cap, flow;} e[5200];
int g[N][N>>1], q[N], p[N], d[N], cur[N], num[N], cnt[N], c, m, n; bool vis[N];

void add_edge(int u, int v, int cap) {
    e[c].u = u; e[c].v = v; e[c].cap = cap; e[c].flow = 0; g[u][cnt[u]++] = c++;
    e[c].u = v; e[c].v = u; e[c].cap = 0; e[c].flow = 0; g[v][cnt[v]++] = c++;
}

bool bfs(int s, int t) {
    memset(vis, 0, sizeof(vis)); memset(d, 0, sizeof(d)); q[0] = t; d[t] = 0; vis[t] = true;
    int head = 0, tail = 1;
    while (head < tail) {
        int v = q[head++];
        for (int i=0; i<cnt[v]; ++i) {
            const edge& ee = e[g[v][i]^1];
            if (!vis[ee.u] && ee.cap > ee.flow) vis[ee.u] = true, d[ee.u] = d[v] + 1, q[tail++] = ee.u;
        }
    }
    return vis[s];
}

void solve() {
    int s = 0, t = m+n+1, cc = 0, u = s; memset(cnt, c = 0, sizeof(cnt));
    for (int i=1; i<=m; ++i) {
        int x; cin >> x; cc += x; add_edge(s, i, x);
        while ((x = cin.peek()) != '\r' && x != '\n') cin >> x, add_edge(i, x+m, INF);
    }
    for (int i=1, x; i<=n; ++i) cin >> x, add_edge(i+m, t, x);
    bfs(s, t); memset(num, 0, sizeof(num)); memset(cur, 0, sizeof(cur));
    for (int i=0; i<=t; ++i) ++num[d[i]];
    while (d[s] <= t) {
        if (u == t) {
            int a = INF;
            for (int v=t; v!=s; v = e[p[v]].u) a = min(a, e[p[v]].cap - e[p[v]].flow);
            for (int v=t; v!=s; v = e[p[v]].u) e[p[v]].flow += a, e[p[v]^1].flow -= a;
            cc -= a; u = s;
        }
        int ok = 0;
        for (int i=cur[u]; i<cnt[u]; ++i) {
            const edge& ee = e[g[u][i]];
            if (ee.cap > ee.flow && d[u] == d[ee.v] + 1) {
                ok = 1; p[ee.v] = g[u][i]; cur[u] = i; u = ee.v;
                break;
            }
        }
        if (!ok) {
            int m = t;
            for (int i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow) m = min(m, d[ee.v]);
            }
            if (--num[d[u]] == 0) break;
            ++num[d[u] = m + 1]; cur[u] = 0;
            if (u != s) u = e[p[u]].u;
        }
    }
    bfs(s, t);
    for (int i=0, k=0; i<cnt[s]; ++i) {
        const edge &ee = e[g[s][i]];
        if (!vis[ee.v]) {
            if (k++) cout << ' ';
            cout << ee.v;
        }
    }
    cout << endl;
    for (int i=0, k=0; i<cnt[t]; ++i) {
        const edge &ee = e[g[t][i]^1];
        if (!vis[ee.u]) {
            if (k++) cout << ' ';
            cout << ee.u-m;
        }
    }
    cout << endl << cc << endl;
}

int main() {
    while (cin >> m >> n) solve();
    return 0;
}

最大密度子图

  [待完善

其他人写的博客

  最详细(也可能现在不是了)网络流建模基础

  最小割模型

  洛谷 网络流24题

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值