Codeforces Round #818 (Div.2)F(最大流)

Codeforces Round #818 (Div.2)F(最大流)

原题链接:https://codeforc.es/contest/1717/problem/F

参考资料:https://zhuanlan.zhihu.com/p/560930180

前言:由于本人刚学完Acwing的网络流课程,但是由于本身codeforces分数不高 ( 某些地方可能理解不到位,请指出,感谢!!!

题意

给定长度为 n 的数组 a[],s[], 和 m 对数对(u , v) ,其中u ,v对应数组a[] 的索引下标(下标从1开始),有以下的操作 (如果是 (u ,v)) 那么即有 b[u] -= 1, b[v] += 1(下标从1开始);并有一个初始化为0,长度为 n 的数组b[]; s[]由’0’ ‘1’ 2种字符构造,要求在经过 m个数对的操作后 。 (1)当s[i] = ‘1’ 时,a[i] == b[i]

(2)当s[i] = ‘0’ 时,不做要求(b[i] 任意为一个数字均可)

输入

第一行输入 n, m

第二行输入 s[]

第三行输入 a[]

接下来的 m 行输入 数对(u ,v)

输出

第一行:如果经过 m 次操作 可以使得 b数组 == a数组,输出"YES" ,反之输出 “NO”

如果 可以构造得出来 b数组,则 接下来的 m 行 输出 构造的的方案

(u , v) 表示 b[u] -= 1, b[v] += 1, 可以交换 前后顺序 —>> (v , u)

思路

(1)转化问题

简化题意:相当于是给出了 m 对操作, 当s[i] = ‘1’ 时,要求构造出 a[i] = b[i]

其中则 m 对操作 分别时 -1 和 +1

那么将 b 变成 a,也相当于 a 变成 b;也就是经过这 m 对操作,使得 当s[i] = ‘1’ 时 a[i] = 0

接下来考虑操作:

对于 一个数对 (u , v) 有 a[u] -= 1, a[v] += 1,那么如果绕一个弯呢?

先让 a[u] += 1, a[v] += 1,然后再让 a[u] -= 2,也能达到相同的效果

这样做有什么好处呢 ?

(1) 可以进行对答案的过滤,因为我们是需要将 a数组,当s[i] = 1的 部分 a[i] = 0的,因此只要先对 a[i] + 1 后,再看看 此刻的 a[i] % 2 的结果:

​ <1> 如果 模数是 ‘1’ 说明无解 输出"NO",因为 -2 不论多少次都无法将这个位置的 a[i] 变成0

​ <2> 如果 模数是 ‘0’ ,此情况合理

<3> 如果 a[i] < 0 ,那么也输出 “NO”, 与 <1> 同理

(2) 那么接下来就是 选择 (m对数对中的2 * m 个待选择的索引 :划分为集合A) 和 (数组中的位置索引i : 划分为集合B) 进行匹配

那么问题就转化为了 网络流中的匹配问题

接下来就是要考虑如何去建图了

<1> 首先建立 S,T

<2> m 对数对中的 2 * m个索引,每个只能被选择一次,所以从 S -> 向 第i个数对连容量是 1的边

<3> 对于第 i 个数对中的 (u , v) 分别有 i - > u + m; i - > v + m,容量均为 1

<4> 对于 (数组中的位置索引i 且 s[i] == 1) ,从它向 T 连一条容量是 a[i] / 2 的边,表示选择(a[i]/2)的意思

<5> 我们还要加一个限制,让A中所有点都用到。记ceCnt为进入A的流量减去B中s[i] 不为0的点出去的流量,则我们只需让这些剩余的流量也都流出去。 对于<4> 它对数字的操作相当于 -1,对于ceCnt 一开始初始化为 m,然后再 - |(-1)st[i] == 1| 的操作,得到 | (- 1) st[i] == 0|的操作的容量

<6>增加一个中间点tmp,让每个s[i]为0的点连接到tmp,流量为INF(>=CeCnt即可),然后tmp连接到汇点,流量为ceCnt。

根据上面建图可得:我画的网络流图
在这里插入图片描述

然后就是求最大流,判断最大流是否等于 m :(表示 - 1的个数)

方案数就是依次枚举 m个数对 i,然后看i ,寻找i 的两个(u , v) 路中哪条漫流了,就代表 选择了 u或v -1 了(具体看代码展示)

C++ 代码

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

const int N = 1e5 + 10, M = N << 1;
int n, m, S, T;
const int INF = 0x3f3f3f3f;
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) // 添加一条边a->b,容量为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;
}

void solve()
{
    cin >> n >> m;
    vector<int> s(n), a(n), b(n);
    vector<pair<int, int>> edges(m);

    for (int i = 0; i < n; i++)
        cin >> s[i];

    for (int i = 0; i < n; i++)
        cin >> a[i];

    for (int i = 0; i < m; i++)
    {
        int a, b;
        cin >> a >> b;
        edges[i].first = a;
        edges[i].second = b;
    }

    for (auto &[u, v] : edges)
    {
        u--;
        v--;
        a[u]++;
        a[v]++;
    }

    int ceCnt = m;
    for (int i = 0; i < n; i++)
    {
        if (!s[i])
            continue;
        if (a[i] < 0 || (a[i] & 1))
        {
            cout << "No" << endl;
            return;
        }
        ceCnt -= a[i] / 2;
    }

    S = n + m;
    T = n + m + 1;
    int tmp = n + m + 2;

    for (int i = 0; i < m; i++)
    {
        auto &[u, v] = edges[i];
        add(S, i, 1);
        add(i, u + m, 1);
        add(i, v + m, 1);
    }

    for (int i = 0; i < n; i++)
    {
        if (s[i])
        {
            add(i + m, T, a[i] / 2);
        }
        else
        {
            add(i + m, tmp, ceCnt);
        }
    }
    add(tmp, T, ceCnt);

    if (ceCnt < 0 || dinic() != m)
    {
        cout << "No" << endl;
        return;
    }

    cout << "Yes" << endl;
    for (int i = 0; i < m; i++)
    {
        auto &[u, v] = edges[i];
        if (f[i * 6 + 2] == 0) //看图上边的标号
            swap(v, u);
        cout << u + 1 << " " << v + 1 << "\n";
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值