2021牛客暑期多校训练营9 J Jam 网络流+二分图匹配

题意

给你一个交叉路口,每一方向均有三个子方向,向左向前向右。每个转向方向上存在不同数量的车,每秒仅能通过一辆。在不相撞的情况下计算这些车通过路口的最小总时间。

分析

基本常识可以知道右转与其他不产生影响,考虑将右转单独进行计算,最后答案取 m a x max max

将每个方向的左转和直行共8个方向进行标号,得到如下图和矩阵。(每个方向都优先标左,顺时针方向标号),其中-1表示右转。

在这里插入图片描述

手推样例可知,共有十二种组合可以同时进行,不会存在三种方向同时进行,最多两种方向同时进行, { { 1 , 2 } , { 1 , 5 } , { 1 , 8 } , { 2 , 3 } , { 2 , 6 } , { 3 , 4 } , { 3 , 7 } , { 4 , 8 } , { 4 , 5 } , { 6 , 5 } , { 6 , 7 } , { 7 , 8 } } ; \{\{1, 2\}, \{1, 5\}, \{1, 8\}, \{2, 3\}, \{2, 6\}, \{3, 4\},\{3, 7\}, \{4, 8\}, \{4, 5\}, \{6, 5\}, \{6, 7\}, \{7, 8\}\}; {{1,2},{1,5},{1,8},{2,3},{2,6},{3,4},{3,7},{4,8},{4,5},{6,5},{6,7},{7,8}};。意思是这两种同时向对应方向行进不会相撞。

考虑是否可以进行某种匹配,使得两个方向上可以同时行进的车数最多,每个方向上的剩余值再单独花费时间。答案=匹配数+未匹配数量。

考虑是否可以使用二分图匹最大权配??这里匹配出的值越多,代表同时进行的数量越多,答案越少。

将上述十二种组合作为十二条边,连成下图。
在这里插入图片描述

手推后发现不是二分图,看了题解才发现可以枚举两条边的权值,减去两条边后剩下的图构成二分图。

选择 { 1 , 8 } , { 4 , 5 } \{1,8\},\{4,5\} {1,8},{4,5}两条边进行枚举权值 v 1 , v 2 v1,v2 v1,v2。剩下的边先进行二分图染色,将点分成两部分, S S S向黑点 i i i v a l [ i ] val[i] val[i]的容量,白点 j j j向汇点 T T T v a l [ j ] val[j] val[j]的容量。剩余的边按照上述十二条边组成的图连容量为 i n f inf inf的边。

d i n i c dinic dinic,得到最大流 m a x _ f l o w max\_flow max_flow,即为最大匹配数量。在残留网络中计算剩余流量 m a x _ n o f l o w max\_noflow max_noflow,即在二分图中为进行匹配的剩余数量。

答案为 a n s = m i n ( a n s , m a x _ f l o w + m a x _ n o f l o w + v 1 + v 2 ) ans=min(ans, max\_flow+max\_noflow+v1+v2) ans=min(ans,max_flow+max_noflow+v1+v2),即向右转的最大值为 r i g h t right right a n s = m a x ( a n s , r i g h t ) ans = max(ans,right) ans=max(ans,right)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define endl '\n'
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }
void input() { freopen("in.txt", "r", stdin), freopen("out.txt", "w", stdout); }

const int N = 100, M = N * 2, inf = 1e8;
vector<pii> edge = {{1, 2}, {1, 5}, {1, 8}, {2, 3}, {2, 6}, {3, 4},
                    {3, 7}, {4, 8}, {4, 5}, {6, 5}, {6, 7}, {7, 8}};
int t, g[N][N];
int target[][5] = {{0, 0, 0, 0, 0},
                   {0, 0, 1, 2, -1},
                   {0, -1, 0, 3, 4},
                   {0, 6, -1, 0, 5},
                   {0, 7, 8, -1, 0}};
vector<int> G[10];
int color[10], vis[10], val[10];
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int 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() {
    memset(d, -1, sizeof(d));
    queue<int> q;
    q.push(S);
    d[S] = 0, cur[S] = h[S];
    while (q.size()) {
        int t = q.front();
        q.pop();
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] == -1 && f[i]) {
                d[j] = d[t] + 1;
                cur[j] = h[j];
                if (j == T) return 1;
                q.push(j);
            }
        }
    }
    return 0;
}
int dfs(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 j = e[i];
        if (d[j] == d[u] + 1 && f[i]) {
            int t = dfs(j, min(f[i], limit - flow));
            if (!t) d[j] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}
int dinic() {
    int res = 0, flow;
    while (bfs())
        while (flow = dfs(S, inf)) res += flow;
    return res;
}
int noflow(){
    int res = 0;
    for(int i = 0; i < idx; i += 2){
        if(e[i] == T || e[i^1] == T || e[i] == S || e[i^1] == S) res += f[i];
    }
    return res;
}
void cal(int u, int col) {
    if (color[u] || vis[u]) return;
    color[u] = col, vis[u] = 1;
    int now = col == 1 ? 2 : 1;
    for (auto i : G[u]) cal(i, now);
}
// 删1-8,和4-5
int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    input();
    for (auto it : edge) { // 建图
        if (it.first == 1 && it.second == 8 || it.first == 4 && it.second == 5) continue;
        G[it.first].push_back(it.second), G[it.second].push_back(it.first);
    }
    cal(1, 1);  // 染色
    cin >> t;
    while (t--) {
        int right = 0, res = inf;
        for (int i = 1; i <= 4; i++)
            for (int j = 1; j <= 4; j++){
                int x; cin>>x;
                if(target[i][j] == -1){
                    right = max(right, x);
                    continue;
                }
                val[target[i][j]] = x;
            }
        int vm1 = min(val[1], val[8]), vm2 = min(val[4], val[5]);
        for(int v1 = 0; v1 <= vm1; v1++){ // 枚举第一个权值
            for(int v2 = 0; v2 <= vm2; v2++){ // 枚举第二个权值
                memset(h, -1, sizeof(h)), idx = 0; // 清图
                S = N - 2, T = N - 1;
                for(int i = 1; i <= 8; i++){
                    int v = val[i];
                    if(i == 1 || i == 8) v -= v1;
                    if(i == 4 || i == 5) v -= v2;
                    if(color[i] == 1) add(S, i, v);
                    else add(i, T, v);
                }
                for(auto it : edge){
                    int u = it.first, v = it.second;
                    if(u == 4 && v == 5 || u == 1 && v == 8) continue;
                    if(color[v] == 1) swap(u, v);
                    add(u, v, inf);
                }

                int max_flow = dinic();
                int max_noflow = noflow(); // 计算残留网络
                res = min(max_flow + max_noflow + v1 + v2, res);
            }
        }
        res = max(res, right);
        cout<<res<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值