网络流建模专题

struct DI {
    ll n, m, S, T;
    ll h[N], ne[M], to[M], f[M], idx;
    void add(ll u, ll v, ll w) {
        ne[idx] = h[u], to[idx] = v, f[idx] = w, h[u] = idx++;
        ne[idx] = h[v], to[idx] = u, f[idx] = 0, h[v] = idx++;
    }
    queue<ll> q;
    ll d[N];
    ll cur[N];
    void init() {
        for (int i = 0; i <= idx; i ++)h[i] = -1;
        idx = 0;
    }
    bool bfs() {
        while (!q.empty()) q.pop();
        q.push(S);
        memset(d, -1, sizeof d);  // d数组是深度,防止绕进环中去。
        d[S] = 0;
        cur[S] = h[S];  //必然第一个弧就是,但是容易忘。
        while (!q.empty()) {
            auto u = q.front();
            q.pop();
            for (ll i = h[u]; ~i; i = ne[i]) {
                ll v = to[i];
                if (f[i] && d[v] == -1) {  //找增广路,所以必须f[i] > 0
                    d[v] = 1 + d[u];
                    cur[v] = h[v];  //只会遍历一次。
                    if (v == T) return 1;
                    q.push(v);
                }
            }
        }
        return 0;
    }
    ll dfs(ll u, ll limit) {
        if (u == T) return limit;  //容易忘。终点直接返回
        ll flow = 0;               //点流向后面的。
        for (ll i = cur[u]; ~i && flow < limit;
             i = ne[i]) {  //从当前弧开始,如果之前
            ll v = to[i];
            cur[u] = i;  //回溯到u点之前,然后又到了u点,没必要从头开始了。
            if (d[v] == d[u] + 1 && f[i]) {  // d是按照层数走,f必须大于0。
                ll t = dfs(v, min(f[i], limit - flow));  // t是走向出去的流量。
                if (t <= 0) d[v] = -1;  //一滴也没有了,点就可以删掉了。
                f[i] -= t;
                f[i ^ 1] += t;  //更新残留网络。
                flow += t;  //当前总的流向后面的加上当前v流向后面的。
            }
        }
        return flow;
    }
    ll dinic() {
        ll ret = 0, flow;
        while (bfs()) {
            while (flow = dfs(S, inf)) ret += flow;
        }
        //init();
        return ret;
    }
    void makemap() {
        init();
        scanf("%d", &n);
        S = 0;
        T = N-1;
        for (int i = 1; i <= n * 2; i ++)add(S, i, 1);
        for (int i = 1; i <= 2 * n; i ++) {
            int u, v;
            scanf("%d%d", &u, &v);
            u += 3*n;
            v += 3*n;
            add(i, u, 1);
            add(i, v, 1);
            
        }
        for (int i = 1; i <= n; i ++) {
            add(i + 3 * n, T, 2);
        }
    }
}dinic;

  • P2754 [CTSC1999]家园 / 星际转移问题
    按照时间建立分层图建模,注意飞船转移的时候当飞船返回也要建立一条边。

  • P2526 [SHOI2001]小狗散步
    发现是一个二分图的关键在于,是否能找到他们是一一对应的关系,这个小狗他每次只出去一个经典,就很无语。

  • P2057 [SHOI2007]善意的投票 / [JLOI2010]冠军调查
    冲突模型最小割,睡觉不睡觉一定会分成两个集合,然后好朋友们之间流量是1,所以建立双向边跑最大流得到最小割,就是把他们分成睡觉和不睡觉最小冲突的分法。

  • [TJOI2013]攻击装置
    经中之典的建模,一些关系不能冲突,求互不冲突最大数量,最大独立集,就是所有点数量减去最大匹配。

  • Penguins LightOJ - 1154-vj
    拆点,会发现,跳跃之间流量要赋成正无穷,所以如果要限制跳跃次数,就应该把一个冰拆成两层,用 \(limit\) 跳跃次数作为流量从底层跳到高层,然后再跳向其他的底层,然后枚举汇点,注意汇点连接的是底层

  • P1251 餐巾计划问题
    如果题目中要求每天都要满足条件,类似的,那么就可以设为将每个要满足的和汇点连边,容量设为应满足的条件。

  • P1345 [USACO5.4]奶牛的电信Telecowmunication
    经中之典最小割,如果要求删去最小数量边,好求。如果要求删去数量的点,那么就要使得每个点拆成出度和入度,之间的流量限制为 \(1\), 然后就可以出度点连接入度点了,权值设为正无穷,注意不要另设一个源汇点,因为如果求最小割,还是点之间的,想想就知道不行。

  • HDU 3605
    经中之典状态压缩,\(n\) 太多了没想到 \(m\) 很小,然后就可以多记录状态了。

  • P1361 小M的作物
    经中之典最小割模型,种两种地方,有不同的收获,并且组合种某种地方也有格外收获,会发现如果从 \(A\)\(B\) 就是最小割模型。

  • P4843 清理雪道
    发现,题目要求每条边必须走过,那么所以一定是所有边流量下界为1,然后流量上界为inf,所以就是建好无源汇上下界可行流,然后搞有源汇上下界最小流。

  • P4043 [AHOI2014/JSOI2014]支线剧情
    经典上下界最小费用可行流,就是定好上下界和费用,然后返回的时候连反向正无穷边。注意,得到新图后,最小费用是先计算好下界流量乘以相应费用,然后在计算附加图的最小费用。

  • P3980 [NOI2008] 志愿者招募
    经典上下界最小费用可行流,但是这个没算原图的流量下界乘费用,因为反边的流量下界为0。

  • P4311 士兵占领
    经典网格图,然后就是最开始的想法是,作为无源汇可行流来解,即只有行的入点出点,列的入点出点,网格上的点,但是这样边和点数量过多。可是如果想象成类似二分图,把行和列拿出来,然后虚拟源点连接行,然后列连接虚拟汇点,然后行和列之间就可以连边,规定好流量上下界即可解决。

  • P4553 80人环游世界
    简单上下界费用流建模。景点国家的出入点决定到这个国家几个人,然后源点连出流量为人数的点到一个小源点,然后从小源点连向每个景点,然后景点之间机票可以连边,从景点的出点连向景点的入点。

  • P1344 [USACO4.4]追查坏牛奶Pollutant Control
    最小割神仙题目,不仅让求最小的割,并且求得的是边的割集的最小权值和,一种神仙做法是,将每个边权转化为, \(w = x \times C + y\),其中 \(\sum y < C\),所以就是 \(y\) 就是1,意义是一条边,然后最后求得答案,割的数量就是 \(maxflow \mod C\),最小边权和就是 \(\frac{maxflow}{C}\).

posted @ 2021-08-13 14:42  u_yan  阅读(42)  评论(0编辑  收藏  举报