CF1687C Sanae and Giant Robot 题解

发布时间 2024-01-10 00:10:29作者: cccpchenpi

题目链接:https://codeforces.com/contest/1687/problem/C

题意简述

有两个长为 \(n\) 的数列 \(a\)\(b\)。有 \(m\) 条线段,你可以进行任意次以下操作:

  • 选择一条线段 \([l, r]\),若 \(\sum\limits_{i = l}^r a_i = \sum\limits_{i = l}^r b_i\),将 \(a\)\([l, r]\) 段置为 \(b\)

问是否能使 \(a\)\(b\) 相同。

题解

这题其实非常简单。首先要转化问题。令两数组差的前缀和 \(s_n = \sum\limits_{i=1}^n (a_i - b_i)\),则操作等价于:

  • \(s_{l-1} = s_r\),将 \(s\)\([l-1,r]\) 段置为 \(s_r\)

问题等价于能否使 \(s\) 变为全 \(0\)

这时问题已经很清晰了,我们倒序考虑操作,不难得到如下结论:

  • 一次 \(s_{l-1} = s_r \ne 0\) 的操作是毫无意义的,也就是操作总有 \(s_{l-1} = s_r = 0\)

  • 那么,既然所有操作都要端点为 \(0\) 时才能操作,且最终的目标也是全 \(0\),只要一次操作可以进行,立即进行总是最优的。且已经执行该操作后,不会需要再次执行。

考虑如何模拟这个过程。我们很容易想到类似拓扑排序的模型,将每条线段置为 \(0\) 时,对每个被置为 \(0\) 的点,判断其出边对应的操作是否可以执行了即可。进行所有操作后,判断 \(s\) 数组是否已经为全 \(0\) 即可。

每个点只会被置零一次。可以使用 set 维护尚未被置零的点,或者使用并查集甚至链表等其他手段保证正确复杂度。时间复杂度 \(O(n \log n)\)\(O(n\alpha(n))\)\(O(n)\)

代码实现

我练习写了一版并查集,但再想想用 set 会方便简洁许多,因此在这里附上更短的代码。

#define int i64
void solve() {
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1), b(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    vector<int> s(n + 1);
    for (int i = 1; i <= n; i++) { s[i] = s[i - 1] + a[i] - b[i]; }
    vector<pair<int, int>> op;
    queue<int> q;
    vector<vector<int>> adj(n + 1);
    set<int> nz;
    for (int i = 1; i <= n; i++) {
        if (s[i]) nz.insert(i);
    }
    for (int i = 0; i < m; i++) {
        int l, r;
        cin >> l >> r;
        l--;
        op.push_back({l, r});
        if (!s[l] && !s[r]) { q.push(i); }
        if (s[l]) adj[l].push_back(i);
        if (s[r]) adj[r].push_back(i);
    }
    while (q.size()) {
        int id = q.front();
        q.pop();
        auto [l, r] = op[id];
        for (auto it = nz.lower_bound(l); it != nz.end() && *it <= r;
             it = nz.erase(it)) {
            int x = *it;
            s[x] = 0;
            for (int u : adj[x]) {
                auto [l, r] = op[u];
                if (!s[l] && !s[r]) q.push(u);
            }
        }
    }
    if (ranges::count(s, 0) != (i64)s.size()) cout << "No\n";
    else cout << "Yes\n";
}
#undef int