网络流练习

dinic

模板

int s, t;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
    int to, nxt, w;
}e[M << 1];
void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w};
    head[u] = cnt;
    e[++ cnt] = {u, head[v], 0};
    head[v] = cnt;
}
bool bfs()
{
    memset(dep, 0, sizeof dep);
    queue<int> q;
    q.push(s); dep[s] = 1; cur[s] = head[s];
    while(q.size())
    {
        int u = q.front(); q.pop();
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to;
            if(e[i].w && !dep[v])
            {
                q.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == t) return true;
            }
        }
    }
    return false;
}
LL dfs(int u = s, LL flow = inf)
{
    if(u == t) return flow;
    int left = flow;
    for(int & i = cur[u]; i && left/*attention*/; i = e[i].nxt)
    {
        int v = e[i].to;
        if(e[i].w && dep[v] == dep[u] + 1)
        {
            LL c = dfs(v, min(e[i].w, left));
            if(!c) dep[v] = 0;
            e[i].w -= c, e[i ^ 1].w += c, left -= c;
        }
    }
    return flow - left;
}
LL dinic()
{
    LL maxflow = 0;
    while(bfs())
        maxflow += dfs();
    return maxflow;
}

407. 稳定的牛分配

题目链接
二分范围,枚举最小值,获取最大值即可。
note: 当前残存容量为0则不再枚举剩余连边。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>
#include <list>
#define LL int
using namespace std;
typedef pair<int, int> PII;
const int N = 1100, B = 30, M = N * 100, inf = 0x3f3f3f3f;
int s, t;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
    int to, nxt, w;
}e[M << 1];
void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w};
    head[u] = cnt;
    e[++ cnt] = {u, head[v], 0};
    head[v] = cnt;
}
bool bfs()
{
    memset(dep, 0, sizeof dep);
    queue<int> q;
    q.push(s); dep[s] = 1; cur[s] = head[s];
    while(q.size())
    {
        int u = q.front(); q.pop();
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to;
            if(e[i].w && !dep[v])
            {
                q.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == t) return true;
            }
        }
    }
    return false;
}
LL dfs(int u = s, LL flow = inf)
{
    if(u == t) return flow;
    int left = flow;
    for(int & i = cur[u]; i && left/*attention*/; i = e[i].nxt)
    {
        int v = e[i].to;
        if(e[i].w && dep[v] == dep[u] + 1)
        {
            LL c = dfs(v, min(e[i].w, left));
            if(!c) dep[v] = 0;
            e[i].w -= c, e[i ^ 1].w += c, left -= c;
        }
    }
    return flow - left;
}
LL dinic()
{
    LL maxflow = 0;
    while(bfs())
        maxflow += dfs();
    return maxflow;
}
int n, b;
int a[N][B], c[B];
bool check(int mi, int mx)
{
    for(int i = 0; i <= t; i ++) head[i] = 0;
    cnt = 1;
    for(int i = 1; i <= n; i ++) add(s, i, 1);
    for(int i = 1; i <= b; i ++) add(i + n, t, c[i]);
    for(int i = 1; i <= n; i ++)
        for(int j = mi; j <= mx; j ++)
            add(i, n + a[i][j], 1);
   
    // cout <<"eeeeeee" << endl;
    // for(int i = 2; i <= cnt; i += 2)
    // {
    //     cout << e[i ^ 1].to << ' ' << e[i].to << endl;
    // } 
    int flow = dinic();
    // cout << mi << ' ' << mx << ' ' << flow << endl;
    return flow == n;
}
int main()
{
    scanf("%d%d", &n, &b);
    s = 0, t = n + b + 1;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= b; j ++) scanf("%d", &a[i][j]);
    for(int i = 1; i <= b; i ++) scanf("%d", &c[i]);
    //枚举范围 再枚举最小值
    int l = 1, r = b, range;
    while(l < r)
    {
        range = (l + r) / 2; 
        // cout << "start " << range << ' ' << /l << " " << r << endl;
        bool fl = false;
        for(int mi = 1; mi + range - 1 <= b; mi ++)
        {
            int mx = mi + range - 1;
            if(check(mi, mx)) 
            {
                fl = true;
                break;
            }
            // cout << "mi " << mi << ' ' << "mx " << mx << endl;
        }
        if(fl) r = range;
        else l = range + 1;
        // cout  << "end " << range << ' ' << l << ' ' << r << endl;
    }
    printf("%d", l);
    return 0;
}

必经边/可行边/不可行边

380. 舞动的夜晚

题目链接
在二分图的最大匹配中:

  1. 必须边在所有最大匹配方案都会成为匹配边;
  2. 可行边是存在一个最大匹配方案使得其成为匹配边;
  3. 不可行边是在所有的最大匹配方案中都不存在的边。

此题要求所有不可行边。
3类边可用网络流求解:

  1. 必须边:是匹配边,并且端点在残存网络中不在一个强连通分量里
  2. 可行边:是匹配边,或者端点在残存网络中在一个强连通分量里
  3. 不可行边:不是匹配边,并且端点在残存网络中不在一个强连通分量里
#include<bits/stdc++.h>
#define LL int
using namespace std;
const int N = 20000 + 10, M = 2e6 + 10, inf = 0x3f3f3f3f;
int n, m, s, t, k;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
    int to, nxt, w;
}e[M];
int dfn[N], low[N], dfn_cnt, c[N], scc_cnt, stk[N], top, ans_cnt, ans[N];
bool ins[N];
void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w};
    head[u] = cnt;
    e[++ cnt] = {u, head[v], 0};
    head[v] = cnt;
}
bool bfs()
{
    memset(dep, -1, sizeof dep);
    memcpy(cur, head, sizeof cur);
    queue<int> q;
    q.push(s);
    dep[s] = 0;
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to, w = e[i].w;
            if(dep[v] == -1 && w > 0)
            {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    return dep[t] != -1;
}
LL dfs(int u = s, LL flow = inf)
{
    if(u == t) return flow;
    LL left = flow;
    for(int & i = cur[u]; i && left; i = e[i].nxt)
    {
        int v = e[i].to, w = e[i].w;
        if(dep[v] == dep[u] + 1 && w > 0)
        {
            LL c = dfs(v, min(left, w));
            left -= c, e[i].w -= c, e[i ^ 1].w += c;
        }
    }
    return flow - left;
}
LL dinic()
{
    LL maxflow = 0;
    while(bfs())
        maxflow += dfs();
    return maxflow;
}
void tarjan(int u)
{
    stk[++ top] = u;
    ins[u] = true;
    dfn[u] = low[u] = ++ dfn_cnt;
    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if(e[i].w == 0) continue;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[v], low[u]);
        }
        else if(ins[v]) low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u])
    {
        ++ scc_cnt;
        int v;
        do
        {
            v = stk[top --];
            ins[v] = false;
            c[v] = scc_cnt;
        }while(v != u);
    }
}
int main()
{
    scanf("%d%d%d", &n, &m, &k);
    s = n + m + 2, t = n + m + 1;
    for(int i = 1; i <= k; i ++)
    {
        int u, v; scanf("%d%d", &u, &v);
        add(u, v + n, 1);
    }
    for(int i = 1; i <= n; i ++) add(s, i, 1);
    for(int i = 1; i <= m; i ++) add(i + n, t, 1);
    dinic();
    for(int i = 1; i <= n + m + 2; i ++)
        if(!dfn[i])
            tarjan(i);
    //不可行边:不是匹配边 并且不在一个强连通分量里
    // for(int i = 2; i <= cnt; i += 2)
    //     printf("u = %d, v = %d, w = %d\n", e[i^1].to, e[i].to, e[i].w);
    // for(int i = 1; i <= n + m; i ++) printf("i = %d, c[i] = %d\n", i, c[i]);
    for(int i = 2; i <= k * 2 + 2; i += 2) //第2条边开始
    {
        int v = e[i].to, u = e[i ^ 1].to, w = e[i].w;
        if(w && c[u] != c[v]) ans[++ ans_cnt] = i / 2;
    }
    if(!ans_cnt) printf("0\n\n");
    else
    {
        printf("%d\n",ans_cnt);
        for(int i = 1; i <= ans_cnt; i ++) printf("%d ", ans[i]);
    }
    return 0;
}

最小割

381. 有线电视网络

题目链接
使得原图不连通,即使得删点后至少某两个点无法连通,可枚举此两点为源点 s s s和汇点 t t t,求最小割后割边端点就是应该删除的点。但网络流求的是割边,因此可以将点 x x x拆成两个点 x x x x + n x + n x+n,在两个点之间连一条边,拆点可以将删点的操作变成割边的操作。
note: 边数和点数通过计算获取不要随便开大,特别是要memset,memcpy的时候。

#include<bits/stdc++.h>
#define LL int
using namespace std;
const int N = 100 + 10, M = 1e4 + 10, inf = 0x3f3f3f3f;
int n, m, s, t;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
    int to, nxt, w;
}e[M], e_[M];

void add(int u, int v, int w)
{
    e_[++ cnt] = {v, head[u], w};
    head[u] = cnt;
    e_[++ cnt] = {u, head[v], 0};
    head[v] = cnt;
}
bool bfs()
{
    for(int i = 0; i < N; i ++) dep[i] = -1, cur[i] = head[i];
    queue<int> q;
    q.push(s);
    dep[s] = 0;
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to, w = e[i].w;
            if(dep[v] == -1 && w > 0)
            {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    return dep[t] != -1;
}
LL dfs(int u = s, LL flow = inf)
{
    if(u == t) return flow;
    LL left = flow;
    for(int & i = cur[u]; i && left; i = e[i].nxt)
    {
        int v = e[i].to, w = e[i].w;
        if(dep[v] == dep[u] + 1 && w > 0)
        {
            LL c = dfs(v, min(left, w));
            left -= c, e[i].w -= c, e[i ^ 1].w += c;
        }
    }
    return flow - left;
}
LL dinic()
{
    LL maxflow = 0;
    while(bfs())
        maxflow += dfs();
    return maxflow;
}
void init()
{
    cnt = 1;
    for(int i = 0; i < N; i ++) head[i] = 0;
}
int main()
{
    while(~scanf("%d %d", &n, &m))
    {
        init();
        for(int i = 1; i <= n; i ++) add(i, i + n, 1);
        for(int i = 1; i <= m; i ++)
        {
            char ch;
            cin >> ch;
            int u, v; scanf("%d,%d", &u, &v);
            cin >> ch;
            u ++, v ++;
            //x为入点 x + n为出点 一条边从出点到入点
            add(u + n, v, inf), add(v + n, u, inf);
        }
        int ans = inf;
        for(s = 1 + n; s <= 2 * n; s ++)
        {
            for(t = 1; t <= n; t ++)
            {
                if(s - n == t) continue;
                for(int i = 0; i < M; i ++) e[i] = e_[i];
                ans = min(ans, dinic());
            }
        }
        printf("%d\n", ans == inf ? n : ans);
    }
    return 0;
}

最大流最小费用

在取得最大流的前提下获得最小费用。在EK算法将 b f s bfs bfs求最短路的过程用 s p f a spfa spfa取代,同时 s p f a spfa spfa也可以求最长路,即可求最大费用。

382. K取方格数

题目链接
note: c n t cnt cnt置1;计算点数和边数。

#include<bits/stdc++.h>
using namespace std;
const int M = 4e4 + 10  , N = 5010, inf=0x3f3f3f3f;
struct E{
    int to, nxt, w, c;
}e[M << 1];
int n, m, s, t, cnt = 1;
bool vis[N];
int maxflow, maxcost, head[N], cur[N], dis[N], incf[N], pre[N];
void add(int u, int v, int w, int c)
{
    e[++ cnt] = {v, head[u], w, c};
    head[u] = cnt;
    e[++ cnt] = {u, head[v], 0, -c};
    head[v] = cnt;
}

bool spfa()
{
    memset(dis, 0xcf, sizeof dis);
    memset(vis, 0, sizeof vis);
    queue<int> q;
    q.push(s);
    dis[s] = 0, vis[s] = true, incf[s] = inf;
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        // cout << "u" << u << endl;
        vis[u] = false;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to;
            if(e[i].w && dis[v] < dis[u] + e[i].c)
            {
                dis[v] = dis[u] + e[i].c;
                incf[v] = min(incf[u], e[i].w);
                pre[v] = i;
                if(!vis[v])
                {
                    vis[v] = true;
                    q.push(v);
                }
            }
        }
    }
    return dis[t] != 0xcfcfcfcf;
}

void MCMF()
{
    while(spfa())
    {
        for(int i = t; i != s; i = e[pre[i] ^ 1].to) //i表示的是节点 表示当前的增广路径
        {
            // cout << "i" << i << endl;
            e[pre[i] ^ 1].w += incf[t];
            e[pre[i]].w -= incf[t];
        }
        maxflow += incf[t];
        maxcost += incf[t] * dis[t];
    }
}
inline int id(int x, int y, int k){return (x - 1) * n + y + k * n * n;}
int main()
{
    int k;
    cin >> n >> k;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
        {
            int value; cin >> value;
            add(id(i, j, 0), id(i, j, 1), 1, value), add(id(i, j, 0), id(i, j, 1), k - 1, 0);
            if(j < n) add(id(i, j, 1), id(i, j + 1, 0), k, 0);
            if(i < n) add(id(i, j, 1), id(i + 1, j, 0), k, 0);
        }
    s = id(1, 1, 0), t = id(n, n, 1);
    MCMF();
    cout << maxcost << endl;
    return 0;
}

P4016 负载平衡问题(费用流)

源点向大于平均值的点连边权为 a b s ( a [ i ] − a v e r ) abs(a[i] - aver) abs(a[i]aver)的边,小于平均值的点向汇点连边权为 a b s ( a [ i ] − a v e r ) abs(a[i]-aver) abs(a[i]aver)的边。相邻的点再连一条边权为 i n f inf inf的边。这样网络流就是 ∑ a [ i ] − a v e r \sum{a[i]-aver} a[i]aver i i i为权值大于 a v e r aver aver的点。大点流出,小点接受,最后相当于每个点都剩下0。而在网络流中每个流量都伴随着一个运输代价。

//mcmf模板
int a[N];
int main()
{
    scanf("%d", &n);
    int sum = 0;
    for(int i = 0; i < n; i ++) {scanf("%d", &a[i]); sum += a[i];}
    sum /= n, t = n, s = n + 1;
    for(int i = 0; i < n; i ++)
    {
        if(a[i] > sum) add(s, i, a[i] - sum, 0);
        if(a[i] < sum) add(i, t, sum - a[i], 0);            
        add(i, (i - 1 + n) % n, inf, 1);
        add(i, (i + 1) % n, inf, 1);
    }
    MCMF();
    printf("%d\n", mincost);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值