网络流学习记录

来源:互联网 发布:智联招聘java简历模板 编辑:程序博客网 时间:2024/05/22 02:13

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为zint 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,之后是饭233int 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\n", (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; }
0 0
原创粉丝点击