网络流(四) 最大流判断,分层图,拆点

网络流(四) 最大流判断,分层图,拆点

最大流判断:秘密挤奶机

经典最大值最小问题,可以直接二分出最大的边权mid,看是否存在一个最大流,其中所有边权都小于等于mid,且流量大于K,不断二分下去即可找到答案。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define int long long
using namespace std;
const int N = 210, M = 80010, inf = 1e18;
int n, m, S, T, K;
int h[N], e[M], f[M], ne[M], w[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, w[idx] = c, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs(){
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while(hh <= tt){
        int t = q[hh ++];
        for(int i = h[t]; i != -1; i = ne[i]){
            int ver = e[i];
            if(d[ver] == -1 && f[i]){
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if(ver == T){
                    return true;
                }
                q[++ tt] = ver;
            }
        }
    }
    return false;
}
int find(int u, int limit){
    if(u == T){
        return limit;
    }
    int flow = 0;
    for(int i = cur[u] ; i != -1 && flow < limit; i = ne[i]){
        cur[u] = i;
        int ver = e[i];
        if(d[ver] == d[u] + 1 && f[i]){
            int t = find(ver, min(f[i], limit - flow));
            if(!t){
                d[ver] = -1;
            }
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t;
        }
        
    }
    return flow;
}
int dinic(){
    int r = 0, flow;
    while(bfs()){
        while(flow = find(S, inf)){
            r += flow;
        }
    }
    return r;
}
bool check(int mid){
    for(int i = 0; i < idx; i ++){
        if(w[i] > mid){
            f[i] = 0;
        }
        else{
            f[i] = 1;
        }
    }
    return dinic() >= K;
}
signed main(){
    scanf("%lld%lld%lld",&n, &m, &K);
    S = 1;
    T = n;
    memset(h, -1, sizeof(h));
    while(m --){
        int a, b, c;
        scanf("%lld%lld%lld",&a, &b, &c);
        add(a, b, c);
    }
    int l = 0, r = 1e9;
    while(l < r){
        int mid = (l + r) >> 1;
        if(check(mid)){
            r = mid;
        }
        else{
            l = mid + 1;
        }
    }
    printf("%lld\n", r);
}

分层图:星际转移问题

当网络流问题中引入了距离概念时,我们可以考虑分层图。我们可以把分出k层图,第 i i i 层表示第 i i i天,源点向每一个“地球”连一条容量为无穷大的边,每个空间站向下一时间的该空间站连一条容量为去穷大的边,代表时间间的转移。

参考代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1101 * 50 + 10, M = (N + 1100 + 20 * 1101) + 10, INF = 1e8;

int n, m, k, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
struct Ship
{
    int h, r, id[30];
}ships[30];
int p[30];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int get(int i, int day)
{
    return day * (n + 2) + i;
}

void add(int a, int b, int c)
{
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

bool bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T) return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;
}

int find(int u, int limit)
{
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i])
    {
        cur[u] = i;
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    S = N - 2, T = N - 1;
    memset(h, -1, sizeof h);
    for (int i = 0; i < 30; i ++ ) p[i] = i;
    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        ships[i] = {a, b};
        for (int j = 0; j < b; j ++ )
        {
            int id;
            scanf("%d", &id);
            if (id == -1) id = n + 1;
            ships[i].id[j] = id;
            if (j)
            {
                int x = ships[i].id[j - 1];
                p[find(x)] = find(id);
            }
        }
    }
    if (find(0) != find(n + 1)) puts("0");
    else
    {
        add(S, get(0, 0), k);
        add(get(n + 1, 0), T, INF);
        int day = 1, res = 0;
        while (true)
        {
            add(get(n + 1, day), T, INF);
            for (int i = 0; i <= n + 1; i ++ )
                add(get(i, day - 1), get(i, day), INF);
            for (int i = 0; i < m; i ++ )
            {
                int r = ships[i].r;
                int a = ships[i].id[(day - 1) % r], b = ships[i].id[day % r];
                add(get(a, day - 1), get(b, day), ships[i].h);
            }
            res += dinic();
            if (res >= k) break;
            day ++ ;
        }

        printf("%d\n", day);
    }

    return 0;
}

拆点:餐饮

当点的使用次数有限制时,可以把该点拆成一个入点, 一个出点,在入点到处点之间连一条边,容量为该点的使用次数。

建图大致如下:

在这里插入图片描述

参考代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 1e3 + 10, M = 1e5 + 10, inf = 1e18;
int n, F, D, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];

void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}

bool bfs(){
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while(hh <= tt){
        int t = q[hh ++];
        for(int i = h[t]; i != -1; i = ne[i]){
            int ver = e[i];
            if(d[ver] == -1 && f[i]){
                cur[ver] = h[ver];
                d[ver] = d[t] + 1;
                if(ver == T){
                    return true;
                }
                q[++ tt] = ver;
            }
        }
    }
    return false;
}

int find(int u, int limit){
    if(u == T){
        return limit;
    }
    int flow = 0;
    for(int i = cur[u]; i != -1 && flow < limit; i = ne[i]){
        cur[u] = i;
        int ver = e[i];
        if(d[ver] == d[u] + 1 && f[i]){
            int t = find(ver, min(limit - flow, f[i]));
            if(!t){
                d[ver] = -1;
            }
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t;
        }
    }
    return flow;
}
int dinic(){
    int r = 0, flow;
    while(bfs()){
        while(flow = find(S, inf)){
            r += flow;
        }
    }
    return r;
}
signed main(){
    scanf("%lld%lld%lld",&n, &F, &D);
    S = 0;
    T = 2 * n + F + D;
    memset(h, -1, sizeof(h));
    for(int i = 2 * n + 1; i <= 2 * n + F; i ++){
        add(S, i, 1);
    }
    for(int i = 2 * n + F + 1; i <= 2 * n + F + D; i ++){
        add(i, T, 1);
    }
    for(int i = 1; i <= n ; i ++){
        add(i, n + i, 1);
        int a, b, t;
        scanf("%lld%lld",&a, &b);
        while(a --){
            scanf("%lld", &t);
            add(n * 2 + t, i , 1);
        }
        while(b --){
            scanf("%lld", &t);
            add(i + n, n * 2 + F + t, 1);
        }
    }
    printf("%lld\n", dinic());
}

拆点:最长不下降子序列问题

由题意得:第一问直接二维dp求最长不下降子序列长度即可。第二问,每个点只能受用一次,问最长不下降子序列有多少个,首先每个点只能使用一次,即对点数有限制,我们对每个点进行拆点。用 f [ i ] f[i] f[i] 表示 以第 i i i 个数结尾,最长不下降子序列的长度,当 j < i j < i j<i a [ j ] < = a [ i ] a[j] <= a[i] a[j]<=a[i] 并且 $f[j] == f[i] - 1 $ 则从 j j j 连一条边到 i i i , 从源点S 向所有 f [ i ] = 1 f[i] = 1 f[i]=1的点连一条边权为1边, 从所有 f [ i ] = = m a x l e n f[i] == maxlen f[i]==maxlen 的点连一条边到汇点t, 再从每个入点连一条边权为1的边到出点。第三问只需要将第二问中的,限制改为无限制(边权为无穷大)即可。

参考代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 1010, M = 3e5+10, inf = 1e18;
int n, S, T;
int h[N], e[M], ne[M], f[M], idx;
int a[N], F[N];
int d[N], q[N], cur[N];
void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs(){
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while(hh <= tt){
        int t = q[hh ++];
        for(int i = h[t]; i != -1; i = ne[i]){
            int ver = e[i];
            if(d[ver] == -1 && f[i]){
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if(ver == T){
                    return true;
                }
                q[++ tt] = ver;
            }
        }
    }
    return false;
}
int find(int u, int limit){
    if(u == T){
        return limit;
    }
    int flow = 0;
    for(int i = cur[u]; i != -1 && flow < limit; i = ne[i]){
        cur[u] = i;
        int ver = e[i];
        if(d[ver] == d[u] + 1 && f[i]){
            int t = find(ver, min(limit - flow, f[i]));
            if(!t){
                d[ver] = -1;
            }
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t;
        }
    }
    return flow;
}
int dinic(){
    int r = 0, flow;
    while(bfs()){
        while(flow = find(S, inf)){
            r += flow;
        }
    }
    return r;
}
signed main(){
    scanf("%lld",&n);
    memset(h, -1, sizeof(h));
    for(int i = 1; i <= n ;i ++){
        scanf("%lld",&a[i]);
    } 
    S = 0;
    T = 2 * n + 1;
    int s = 0;
    for(int i = 1; i <= n ; i ++){
        add(i, n + i, 1);
        F[i] = 1;
        for(int j = 1; j < i ; j ++){
            if(a[j] <= a[i]){
                F[i] = max(F[i], F[j] + 1);
            }
        }
        for(int j = 1; j < i ; j ++){
            if(a[j] <= a[i] && F[j] + 1 == F[i]){
                add( n+j, i, 1);
            }
        }
        s = max(s, F[i]);
        if(F[i] == 1){
            add(S, i, 1);
        }
    }
    for(int i = 1; i <= n ; i ++){
        if(F[i] == s){
            add(n + i, T, 1);
        }
    }
    printf("%lld\n", s);
    if(s == 1){
        printf("%lld\n%lld\n", n, n);
    }
    else{
        int res = dinic();
        printf("%lld\n", res);
        for(int i = 0; i < idx ; i += 2){
            int a = e[i ^ 1];
            int b = e[i];
            if(a == S && b == 1){
                f[i] = inf;
            }
            else if(a == 1 && b == n + 1){
                f[i] = inf;
            }
            else if(a == n && b == 2 * n){
                f[i] = inf;
            }
            else if(a == 2 * n && b == T){
                f[i] = inf;
            }
        }
        printf("%lld\n", res + dinic());
    }
}



蜥蜴

由题意得:蜥蜴从起始柱子跳出边界时就算逃脱,直接从所有到边界距离小于等于D的柱子的出点向汇点连一条边权为无穷大的点(因为当能够流到柱子的出点时表示能够作为起跳点)。从每个柱子的入点向出点连一条边权为该柱子起始高度的边,从源点向所有初始时刻有蜥蜴的柱子连一条边权为蜥蜴个数边。

参考代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <map>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e3 + 10, M = 2e5 + 10, inf = 1e18;
const double eps = 1e-8;
int h[N], e[M], ne[M], f[M], idx;
int q[N], d[N], cur[N];
int n, m, S, T, cnt_n, cnt_m;
PII zhuzi[M];
double D;
map<PII, int> pp;
string xiyi1[N], high1[N];
double check(PII a, PII b)
{
    double dx = a.first - b.first;
    double dy = a.second - b.second;
    return dx * dx + dy * dy < D * D + eps;
}
void add(int a, int b, int c)
{
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}
bool bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof(d));
    q[0] = S;
    d[S] = 0;
    cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh++];
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T)
                {
                    return true;
                }
                q[++tt] = ver;
            }
        }
    }
    return false;
}
int find(int u, int limit)
{
    if (u == T)
    {
        return limit;
    }
    int flow = 0;
    for (int i = cur[u]; i != -1 && flow < limit; i = ne[i])
    {
        cur[u] = i;
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(limit - flow, f[i]));
            if (!t)
            {
                d[ver] = -1;
            }
            f[i] -= t;
            f[i ^ 1] += t;
            flow += t;
        }
    }
    return flow;
}
int dinic()
{
    int r = 0, flow;
    while (bfs())
    {
        while (flow = find(S, inf))
        {
            r += flow;
        }
    }
    return r;
}

signed main()
{
    cin >> n >> m >> D;
    memset(h, -1, sizeof(h));
    for (int i = 1; i <= n; i++)
    {
        cin >> high1[i];
        high1[i] = " " + high1[i];
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> xiyi1[i];
        xiyi1[i] = " " + xiyi1[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (high1[i][j] >= '1' && high1[i][j] <= '9')
            {
                zhuzi[++cnt_n] = {i, j};
                pp[{i, j}] = cnt_n;
            }
        }
    }
    S = 0;
    T = cnt_n * 2 + 1;
    for(int i = 1; i <= cnt_n; i ++){
        if(zhuzi[i].first <= D || zhuzi[i].first + D > n || zhuzi[i].second <= D || zhuzi[i].second + D > m){
            add(i + cnt_n, T, inf);
        }
        add(i, i + cnt_n, (high1[zhuzi[i].first][zhuzi[i].second] - '0'));
    }
    int sum = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (xiyi1[i][j] == 'L')
            {
                sum ++;
                add(S, pp[{i, j}], 1);
            }
        }
    }
    for(int i = 1; i <= cnt_n; i ++){
        for(int j = 1; j <= cnt_n; j ++){
            if(i == j){
                continue;
            }
            if(check(zhuzi[i], zhuzi[j])){
                add(i + cnt_n, j, inf);
            }
        }
    }
    int ans = dinic();
    //printf("%lld\n", dinic());
    printf("%lld\n", sum - ans);
    /*
    for(int i = 1; i <= n ; i ++){
        cout << high1[i] << endl;
    }
    for(int i = 1; i <= n ; i ++){
        cout << xiyi1[i] << endl;
    }
    */
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值