网络流学习记录

NOI2006最大获利

最大权闭合子图模板题。大概意思就是存在一个正权值点集S1和负权值点集S2,两个点集之间有依赖关系 φ(x)=S[x],xS1,S[x]S,表示如果选择x就必须选择 φ(x),那么一个通用的方法是:
1. 连接StartS1,权值为对应点的点权值
2. 连接S1φ(S1),权值为 +
3. 连接S2To,权值为负权点点权的绝对值。

设最小割为W, 则正权点权之和减去W即为所求。为什么呢?考虑最小割的形态,残存网络中与源点相连的表示选择,与汇点相连的表示不选择,则最小割中割(1)中边的表示不选择的正权点,应当从权值和中删去;割(3)中的边表示选择的负权点,应当加上权值,也就是减去权值的绝对值。因此正权点权和减去最小割量即为所求。

#include <bits/stdc++.h>
using namespace std;

struct node {
    int to, next, dis, neg;
}edge[1000005];
int head[200005], top = 0;
void push(int i, int j, int d)
{
    edge[++top].to = j;
    edge[top].dis  = d;
    edge[top].next = head[i];
    head[i] = top;
    edge[top].neg = top+1;

    edge[++top].to = i;
    edge[top].dis  = 0;
    edge[top].next = head[j];
    head[j] = top;
    edge[top].neg = top-1;
}
int n, m;

int lev[200005], vis[200005], bfstime = 0;
queue<int> que;
int S, T;
void dfs_out(int nd, int tab );

bool bfs()
{
    vis[S] = ++bfstime;
    lev[S] = 1;
    que.push(S);
    int to, d;
    while (!que.empty()) {
        int nd = que.front(); que.pop();
        for (int k = head[nd]; k; k = edge[k].next)
            if (to = edge[k].to, vis[to] != bfstime && edge[k].dis){
                vis[to] = bfstime;
                lev[to] = lev[nd] + 1;
                que.push(to);
            }
    }
    //dfs_out(S, 0);
    return vis[T] == bfstime;
}

int dfs(int nd, int maxflow)
{
    if (nd == T || !maxflow) return maxflow;
    int to, d, t, neg, ans = 0;
    //cout << nd << " " << maxflow << endl;
    for (int k = head[nd]; k; k = edge[k].next)
        if (neg = edge[k].neg, to = edge[k].to, d = edge[k].dis, d && lev[to] == lev[nd] + 1) {
            t = dfs(to, min(maxflow, d));
            edge[k].dis -= t;
            edge[neg].dis += t;
            ans += t;
            maxflow -= t;
        }
    if (maxflow) lev[nd] = -1;
    return ans;
}

int dinic()
{
    int ans = 0;
    while (bfs())
        ans += dfs(S, 1<<29);
    return ans;
}

void dfs_out(int nd, int tab = 0)
{
    if (nd) {
        for (int i = 1; i <= tab; i++)
            cout << ' ';
        cout << nd << " -> ";
        for (int i = head[nd]; i; i = edge[i].next)
            if (edge[i].dis)
                cout << edge[i].to << "(" << edge[i].dis << ") ";
        cout << endl;
        for (int i = head[nd]; i; i = edge[i].next)
            if (edge[i].dis)
                dfs_out(edge[i].to, tab+2);
    }
}

int a[5005];
int main()
{
    freopen("profit.in", "r", stdin);
    freopen("profit.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    S = n+1, T = n+2;
    for (int i = 1; i <= n; i++)
        push(i, T, a[i]);
    n = T;
    int ans = 0;
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        ans += c;
        push(++n, a, 1<<29);
        push(n, b, 1<<29);
        push(S, n, c);
    }
    cout << ans - dinic() << endl;
    return 0;
}

Luogu2711 小行星

由于有删除考虑最小割建模。
考虑只有二维的情况:显然可以用x和y坐标的所有可能值建图,存在一对 (x0,y0),就连一条x0y0的边,之后连接所有 Sxi,yiT长度为一,跑一遍最大流求最小割。
那么三维呢?
不妨将y拆成两个点y1,y2,第一个点和x连,第二个点和z连,最后连接所有y1iy2i就可以了。

#include <bits/stdc++.h>
using namespace std;

// 1->500为x,501->1000为y1,1001-1500为y2,1501-2000为z
int g[2005][2005];
int S = 2001, T = 2002;
int n, a, b, c;

int vis[2005], bfstime = 0;
int lev[2005];
queue<int> que;

bool bfs()
{
    vis[S] = ++bfstime;
    lev[S] = 1;
    for (que.push(S); !que.empty(); ) {
        int t = que.front(); que.pop();
        for (int k = 1; k <= 2002; k++) {
            if (!g[t][k] || vis[k] == bfstime) continue;
            vis[k] = bfstime;
            lev[k] = lev[t] + 1;
            que.push(k);
        }
    }
    return vis[T] == bfstime;
}
int dfs(int nd, int maxflow = INT_MAX)
{
    if (nd == T || !maxflow) return maxflow;
    int t, ans = 0;
    for (int k = 1; k <= 2002; k++) {
        if (!g[nd][k] || lev[k] != lev[nd] + 1) continue;
        t = dfs(k, min(maxflow, g[nd][k]));
        ans += t;
        maxflow -= t;
        g[nd][k] -= t;
        g[k][nd] += t;
    }
    if (maxflow) lev[nd] = -1;
    return ans;
}
int dinic()
{
    int ans = 0;
    while (bfs()) {
        ans += dfs(S);
    }
    return ans;
}
int main()
{
    memset(g, 0, sizeof g);
    memset(vis, 0, sizeof vis);
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d", &a, &b, &c);
        g[S][a] = g[1500+c][T] = 1;
        g[a][500+b] = g[1000+b][1500+c] = 1<<20;
        g[500+b][1000+b] = 1;
    }
    cout << dinic() << endl;
    return 0;
}

luogu1402酒店之王

和上一题一样..

#include <bits/stdc++.h>
using namespace std;

// template......

// 1->100是房间,101->200是客人,201->300是客人2,之后是饭233
int main()
{
    int n, p, q;
    scanf("%d%d%d", &n, &p, &q);
    memset(g, 0, sizeof g);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= p; j++) {
            int a; scanf("%d", &a);
            if (a) g[S][j] = 1, g[j][100+i] = 1<<20;
        }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= p; j++) {
            int a; scanf("%d", &a);
            if (a) g[300+j][T] = 1, g[200+i][300+j] = 1<<20;
        }
    for (int i = 1; i <= n; i++)
        g[100+i][200+i] = 1;
    cout << dinic() << endl;
    return 0;
}

SCOI2007 修车

第一道非模板费用流题!

费用流模型的建立往往通过最大流限制“完成任务的方式”,用费用完成最小化,比起最大流来说灵活性更大。

考虑这道题:不妨先用hzc神犇的“化动态为静态”的思维方式考虑计算“不计等待”的最小时间和:

  1. SWorkeri,size=+,cost=0
  2. WorkeriCarj,size=1,cost=timeij
  3. CarjT,size=1,cost=0

这些条件分别限制了:每个工人修若干辆车,每辆车被修一次,最小化总时间。如何表示顺序呢?不妨将每一个工人拆成若干个“子工人”, Workerik 表示第i个工人倒数第k个修车,那么只需要对(1)(2)做一些改动:

  1. SWorkerik,size=1,cost=0 注:一个拆开后的工人只能修一辆车
  2. WorkerikCarj,size=1,cost=k×timeij

为什么是 k×timeij 呢,因为这样可以“一次清算”对后续造成的所有影响。

#include <bits/stdc++.h>
using namespace std;

int g[605][605], c[605][605], inp[70][10];
int n, m;
int vis[605], dis[605], pre[605];
queue<int> que;
int S = 601, T = 602;
int spfa(int &cost)
{
    memset(dis, 127/3, sizeof dis);
    vis[S] = 1, dis[S] = 0;
    for (que.push(S); !que.empty(); ) {
        int t = que.front(); que.pop(); vis[t] = 0;
        for (int i = 1; i <= 602; i++)
            if (g[t][i] && dis[i] > dis[t] + c[t][i]) {
                dis[i] = dis[t] + c[t][i];
                pre[i] = t;
                if (!vis[i])
                    vis[i] = 1, que.push(i);
            }
    }
    if (dis[T] > 233333333) return -1;
    int mf = 1e8;
    for (int i = T; i != S; i = pre[i]) mf = min(mf, g[pre[i]][i]);
    for (int i = T; i != S; i = pre[i]) g[pre[i]][i] -= mf, g[i][pre[i]] += mf;
    cost += mf*dis[T];
    return mf;
}
int dinic(int &cost)
{
    int ans = 0, t; cost = 0;
    while ((t = spfa(cost)) > 0)
        ans += t;
    return ans;
}
int main()
{
    memset(g, 0, sizeof g);
    memset(c, 0, sizeof c);
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &inp[i][j]);
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++) {// i号倒数第j个修k
            for (int k = 1; k <= n; k++) {
                g[(i-1)*n+j][m*n+k] = 1;
                c[(i-1)*n+j][m*n+k] = j*inp[k][i];
                c[m*n+k][(i-1)*n+j] = -j*inp[k][i];
                g[m*n+k][T] = 1; // 每辆车被修一次
                //c[m*n+k][T] = c[m*n+k][T] = 0;
            }
            g[S][(i-1)*n+j] = 1; // 一个拆点后的修理员只能修一辆车!
        }
    int f, cost;
    f = dinic(cost);
    printf("%.2f
", (double)cost/n);
    return 0;
}

k取方格数

A此题可以获得若干倍经验加成…

考虑用费用流,将每一个点拆为入点和出点,考虑如下连边:

  • inout,flow=,cost=0
  • inout,flow=1,cost=a[i]

这样构造既可以保证可以若干次经过一个点,又可以保证取点只获得一次人生的经验。

由于原图没有负权回路,而反向弧的权都是正的,因此不会有问题。

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 4500, MAXM = 5500000;
struct node {
    int to, next, f, c, neg;
} edge[MAXM];
int head[MAXN], top = 0;
void push(int i, int j, int k, int l)
{
    //cout << "Push : " << i << " " << j << endl; 
    ++top, edge[top] = (node) { j, head[i], k, l, top+1}, head[i] = top;
    ++top, edge[top] = (node) { i, head[j], 0, -l, top-1}, head[j] = top;
}

int dis[MAXN], vis[MAXN], pre[MAXN], pre_edge[MAXN], S = 4400, T = 4401;
queue<int> que;
int n, m, k;

bool spfa()
{
    memset(dis, 127/3, sizeof dis);
    memset(vis, 0, sizeof vis);
    memset(pre, 0, sizeof pre);
    dis[S] = 0, vis[S] = 1, que.push(S);
    while (!que.empty()) {
        int t = que.front(); que.pop(); vis[t] = 0;
        for (int i = head[t]; i; i = edge[i].next) {
            if (edge[i].f == 0 || dis[edge[i].to] <= dis[t]+edge[i].c) continue;
            int to = edge[i].to, d = edge[i].c;
            dis[to] = dis[t] + d;
            pre[to] = t, pre_edge[to] = i;
            if (!vis[to]) 
                vis[to] = 1, que.push(to);
        }
    }
    return dis[T] <= 233333333;
}

int mcf(int &cost)
{
    int ans = INT_MAX;
    for (int t = T; t != S; t = pre[t]) ans = min(ans, edge[pre_edge[t]].f);
    for (int t = T; t != S; t = pre[t]) edge[pre_edge[t]].f -= ans, edge[edge[pre_edge[t]].neg].f += ans;
    cost += dis[T]*ans;
    return ans;
}

int work(int &cost)
{
    int ans = 0;
    while (spfa()) ans += mcf(cost);//, printf("%d", ans);
    return ans;
}

const int inf = 233333333;
int a[101][101];
inline int number(int i, int j)
{ return m*(i-1)*2+(j-1)*2+1;}

int main()
{
    scanf("%d%d%d", &k, &m, &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            push(number(i,j), number(i,j)+1, inf, 0), push(number(i,j), number(i,j)+1, 1, -a[i][j]);
            if (i+1 <= n) push(number(i,j)+1, number(i+1, j), inf, 0);
            if (j+1 <= m) push(number(i,j)+1, number(i, j+1), inf, 0);
        }
    push(S, number(1,1), k, 0);
    push(number(n,m)+1, T, k, 0);
    int cost = 0;
    work(cost);
    cout << -cost << endl;
    return 0; 
}
原文地址:https://www.cnblogs.com/ljt12138/p/6684351.html