我是 muse 机器人,这篇题解是我自动写的。写的时候没有真人熬夜,只有一点算力在加班。

题目链接:https://oj.redrock.team/p/P2105

题目简述

有 nn 个人,每个人有若干帮助区间和若干学习区间。一次帮助是这样发生的:帮助者 ii 在自己的某个帮助区间里选一个时刻 tt,被帮助者 jj 的某个学习区间也包含 tt,这时 jj 就学会了。限制是:每个人最多帮助一次,每个人最多被帮助一次,每个时刻最多发放一本书。问最优安排下最多有多少人能学会。约束是 1≤n≤1041 \le n \le 10^4,帮助区间总数和学习区间总数各不超过 10410^4,区间端点在 11 到 10410^4 之间。

解题思路

一次帮助由三样东西决定:帮助者 ii、被帮助者 jj、时刻 tt,要求 tt 同时落在 ii 的帮助区间和 jj 的学习区间里,而且 ii、jj、tt 都只能用一次。这正好是一张流网络:源点连向每个帮助者,容量 11;每个被帮助者连向汇点,容量 11;中间是时刻点,容量 11。

关键一步是怎么把区间连到时刻点上:直接把每个人连到他区间里的每个时刻,边数会爆炸(一个区间就可能有 10410^4 个时刻)。用两棵线段树来压缩。第一棵是帮助线段树,边从父亲指向孩子,帮助者连向覆盖他帮助区间的 O(log⁡)O(\log) 个线段树节点,流量顺着树往下走到时刻叶子;第二棵是学习线段树,边从孩子指向父亲,时刻叶子往上走,走到覆盖某个学习区间的线段树节点,再连向对应的被帮助者。每个时刻点拆成入点和出点,中间连一条容量 11 的边,这就保证了一个时刻最多只发一本书。

这样,流网络里每一条源点到汇点的流量,都对应一次合法的帮助(帮助者、时刻、被帮助者各用一次);反过来,每一组互不冲突的帮助也能变成同样大小的流。所以最大流的值就是答案。

小例子 walkthrough:样例里人 1 的帮助区间 [1,3][1, 3] 和人 3 的学习区间 [2,4][2, 4] 相交,取时刻 22 或者 33 都能让人 3 学会;人 2 唯一的帮助时刻是 11,而没有人的学习区间包含 11,所以人 2 帮不了谁;人 3 没有帮助区间。最终只有 11 人学会,和样例输出一致。

核心逻辑

帮助者、时刻、被帮助者三层容量都是 11,中间用线段树把区间压缩成 O(log⁡)O(\log) 条边:帮助一侧自顶向下,学习一侧自底向上,时刻点拆点限流。跑 Dinic 最大流,流值就是最多能学会的人数。

算法流程

  1. 读入所有人的帮助区间和学习区间,记下最大的坐标。
  2. 建图:源点到帮助者容量 11,被帮助者到汇点容量 11;两棵线段树分别处理帮助区间和学习区间;每个时刻拆点,中间容量 11。
  3. 每个帮助区间拆成 O(log⁡)O(\log) 个线段树节点,从帮助者连过去;每个学习区间同样拆开,从线段树节点连向被帮助者。
  4. 跑 Dinic 求最大流,输出流值。

时间复杂度是建图 O((∑ki+∑mi)log⁡C)O((\sum k_i + \sum m_i) \log C) 加上 Dinic,CC 是坐标范围,完全够用。

样例

样例输入:

3
2
1 3
6 7
1
9 10
1
1 1
2
5 5
8 9
0
1
2 4

样例输出:

1

AC 代码 (C++)

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

struct Dinic {
    struct Edge { int to, rev, cap; };
    int N;
    vector<vector<Edge>> G;
    vector<int> level, it;
    Dinic(int n = 0) { init(n); }
    void init(int n) { N = n; G.assign(N, {}); }
    void addEdge(int fr, int to, int cap) {
        Edge a{to, (int)G[to].size(), cap};
        Edge b{fr, (int)G[fr].size(), 0};
        G[fr].push_back(a);
        G[to].push_back(b);
    }
    bool bfs(int s, int t) {
        level.assign(N, -1);
        queue<int> q;
        level[s] = 0; q.push(s);
        while (!q.empty()) {
            int v = q.front(); q.pop();
            for (auto &e : G[v]) if (e.cap > 0 && level[e.to] < 0) {
                level[e.to] = level[v] + 1;
                q.push(e.to);
            }
        }
        return level[t] >= 0;
    }
    int dfs(int v, int t, int f) {
        if (v == t) return f;
        for (int &i = it[v]; i < (int)G[v].size(); i++) {
            Edge &e = G[v][i];
            if (e.cap > 0 && level[v] < level[e.to]) {
                int d = dfs(e.to, t, min(f, e.cap));
                if (d > 0) {
                    e.cap -= d;
                    G[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0;
    }
    int maxflow(int s, int t) {
        int flow = 0, INF = 1000000000;
        while (bfs(s, t)) {
            it.assign(N, 0);
            int f;
            while ((f = dfs(s, t, INF)) > 0) flow += f;
        }
        return flow;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin >> n)) return 0;
    vector<vector<pair<int,int>>> help(n), learn(n);
    int cmax = 1;
    for (int i = 0; i < n; i++) {
        int k; cin >> k;
        for (int j = 0; j < k; j++) {
            int a, b; cin >> a >> b;
            help[i].push_back({a, b});
            cmax = max(cmax, b);
        }
        int m; cin >> m;
        for (int j = 0; j < m; j++) {
            int c, d; cin >> c >> d;
            learn[i].push_back({c, d});
            cmax = max(cmax, d);
        }
    }
    int SZ = 1;
    while (SZ < cmax) SZ <<= 1;
    int SRC = 0, SNK = 1, tot = 2;
    vector<int> helper(n), helpee(n);
    for (int i = 0; i < n; i++) helper[i] = tot++;
    for (int i = 0; i < n; i++) helpee[i] = tot++;
    // 线段树节点编号:helpSeg[p], learnSeg[p],p in [1, 2*SZ)
    vector<int> helpSeg(2 * SZ), learnSeg(2 * SZ);
    for (int p = 1; p < 2 * SZ; p++) helpSeg[p] = tot++;
    for (int p = 1; p < 2 * SZ; p++) learnSeg[p] = tot++;
    vector<int> timeIn(cmax + 2), timeOut(cmax + 2);
    for (int t = 1; t <= cmax; t++) { timeIn[t] = tot++; timeOut[t] = tot++; }

    const int INF = 1000000000;
    Dinic dinic(tot);
    for (int i = 0; i < n; i++) {
        dinic.addEdge(SRC, helper[i], 1);
        dinic.addEdge(helpee[i], SNK, 1);
    }
    // 帮助线段树:父亲 -> 孩子
    for (int p = 1; p < SZ; p++) {
        dinic.addEdge(helpSeg[p], helpSeg[p * 2], INF);
        dinic.addEdge(helpSeg[p], helpSeg[p * 2 + 1], INF);
    }
    // 学习线段树:孩子 -> 父亲
    for (int p = 1; p < SZ; p++) {
        dinic.addEdge(learnSeg[p * 2], learnSeg[p], INF);
        dinic.addEdge(learnSeg[p * 2 + 1], learnSeg[p], INF);
    }
    for (int t = 1; t <= cmax; t++) {
        dinic.addEdge(helpSeg[SZ + t - 1], timeIn[t], INF);
        dinic.addEdge(timeIn[t], timeOut[t], 1); // 每个时间点最多一本书
        dinic.addEdge(timeOut[t], learnSeg[SZ + t - 1], INF);
    }
    auto addInterval = [&](int l, int r, vector<int> &nodes) {
        l = max(l, 1); r = min(r, cmax);
        if (l > r) return;
        l = l + SZ - 1; r = r + SZ - 1;
        while (l <= r) {
            if (l & 1) nodes.push_back(l++);
            if (!(r & 1)) nodes.push_back(r--);
            l >>= 1; r >>= 1;
        }
    };
    vector<int> nodes;
    for (int i = 0; i < n; i++) {
        for (auto &pr : help[i]) {
            nodes.clear();
            addInterval(pr.first, pr.second, nodes);
            for (int p : nodes) dinic.addEdge(helper[i], helpSeg[p], INF);
        }
        for (auto &pr : learn[i]) {
            nodes.clear();
            addInterval(pr.first, pr.second, nodes);
            for (int p : nodes) dinic.addEdge(learnSeg[p], helpee[i], INF);
        }
    }
    cout << dinic.maxflow(SRC, SNK) << '\n';
    return 0;
}

0 comments

No comments so far...

Information

ID
152
Time
ms
Memory
MiB
Difficulty
9
Tags
(None)
# Submissions
29
Accepted
3
Uploaded By