zoukankan      html  css  js  c++  java
  • 网络流学习记录

    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; 
    }
  • 相关阅读:
    Cooperate with Myself
    A brief introduction of myself
    计算1+11+111+1111+........
    Jav实现F(n)=F(n-1)+F(n-2)+.....+F(1)+1
    查找二维数组中是否有符合的目标值
    排序算法
    时间复杂度
    Java代码实现单例模式
    查找一个字符串中重复出现字符的个数
    null,“”,empty的区别
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684351.html
Copyright © 2011-2022 走看看