「JOISC 2016 Day 2」雇佣计划 题解

发布时间 2023-08-15 19:20:52作者: User-Unauthorized

题面

JOI 社为了扩大业务而开始了新社员招募。社员有 \(N\) 名候补者,编号从 \(1\)\(N\),每名候补者有称为评价值的一个确定整数。评价值高于某一个值的候补者全部都将被聘用,他们还将分为几个组别。如果 \(a, b(a \lt b)\) 同时被聘用且 \(c(a \le c\le b)\) 全部被聘用时,\(a,b\) 进入同一组。

你要处理 \(M\) 个查询,查询有以下两种:

  • 评价值 \(B_j\) 以上的候补者全部聘用时的组数;

  • 将候补者 \(C_j\) 的评价值更新为 \(D_j\)

题解

本题存在根号复杂度做法。

思考过程相对朴素,考虑将询问转化为一个二元组 \(\left(time, x\right)\) 表示在第 \(time\) 个修改操作后询问评价值 \(x\) 的组数。

考虑莫队,以 \(time\) 所在块编号为第一关键字,\(x\) 的大小为第二关键字进行排序。答案的处理则可以维护一个 \(\text{bitset}\),每次修改时判断两侧点的状态来决定对答案贡献的影响。

注意一点,排序只能按上文所说的方法进行排序,因为单次修改的复杂度为严格的 \(\mathcal{O}(1)\),而 \(x\) 这一边界移动的复杂度为均摊 \(\mathcal{O}(1)\),如果颠倒排序顺序则无法保证复杂度。关于\(x\) 这一边界移动的复杂度为均摊 \(\mathcal{O}(1)\) 的证明可以考虑将 \(x\) 从最小值移动到最大值每个候补者状态至多更新一次。

该种做法如果想在 \(\text{LOJ}\) 上通过(应该)需要精湛的卡常技巧。笔者秉持着人傻常数大的人生理念成功被卡常。

Code

#include <bits/stdc++.h>

typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::set<valueType> ValueSet;
typedef std::vector<ValueSet> SetVector;
typedef std::vector<bool> bitset;

constexpr valueType MAXN = 2e5 + 5;

class Recorder {
private:
    valueType N, ans;
    bitset data;

public:
    explicit Recorder(valueType n) : N(n), ans(0), data(n + 1, false) {}

    void set(valueType i) {
        data[i] = true;

        if (i > 1 && data[i - 1] && i < N && data[i + 1])
            --ans;
        else if ((i == 1 || !data[i - 1]) && (i == N || !data[i + 1]))
            ++ans;
    }

    void reset(valueType i) {
        data[i] = false;

        if (i > 1 && data[i - 1] && i < N && data[i + 1])
            ++ans;
        else if ((i == 1 || !data[i - 1]) && (i == N || !data[i + 1]))
            --ans;
    }

    valueType get() const {
        return ans;
    }

    void set(ValueSet const &pos) {
        for (auto const &iter: pos)
            set(iter);
    }

    void reset(ValueSet const &pos) {
        for (auto const &iter: pos)
            reset(iter);
    }
};

std::array<valueType, MAXN> belong;

struct Modify {
    valueType pos, pre, to;

    Modify() = default;

    Modify(valueType p, valueType pre, valueType to) : pos(p), pre(pre), to(to) {}
};

struct Query {
    valueType time, key, id;

    Query() = default;

    Query(valueType t, valueType k, valueType i) : time(t), key(k), id(i) {}

    bool operator<(Query const &other) const {
        if (belong[time] != belong[other.time])
            return belong[time] > belong[other.time];

        if (belong[time] & 1)
            return key > other.key;
        else
            return key < other.key;
    }
};

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

    valueType N, M;

    std::cin >> N >> M;

    valueType const Block = std::ceil(std::pow(M, 0.5));

    for (valueType i = 1; i <= M; ++i)
        belong[i] = (i - 1) / Block + 1;

    ValueVector source(N + 1), bucket({0, INT_MIN, INT_MAX});

    bucket.reserve(N + M + 3);

    for (valueType i = 1; i <= N; ++i) {
        std::cin >> source[i];

        bucket.push_back(source[i]);
    }

    std::vector<Modify> modify(1, Modify(0, INT_MIN, INT_MIN));
    std::vector<Query> query;

    modify.reserve(M);
    query.reserve(M);

    for (valueType i = 1; i <= M; ++i) {
        int type;

        std::cin >> type;

        if (type == 1) {
            valueType x;

            std::cin >> x;

            query.emplace_back(modify.size() - 1, x, query.size());

            bucket.push_back(x);
        } else {
            valueType x, y;

            std::cin >> x >> y;

            modify.emplace_back(x, y, source[x]);

            source[x] = y;

            bucket.push_back(y);
        }
    }

    std::sort(bucket.begin(), bucket.end());
    bucket.erase(std::unique(bucket.begin(), bucket.end()), bucket.end());

    auto const S = (valueType) bucket.size() - 1;

    SetVector pos(S + 1);

    for (valueType i = 1; i <= N; ++i) {
        source[i] = (valueType) std::distance(bucket.begin(),
                                              std::lower_bound(bucket.begin(), bucket.end(), source[i]));

        pos[source[i]].insert(i);
    }

    for (auto &iter: modify) {
        iter.pre = (valueType) std::distance(bucket.begin(),
                                             std::lower_bound(bucket.begin(), bucket.end(), iter.pre));

        iter.to = (valueType) std::distance(bucket.begin(),
                                            std::lower_bound(bucket.begin(), bucket.end(), iter.to));
    }

    for (auto &iter: query)
        iter.key = (valueType) std::distance(bucket.begin(),
                                             std::lower_bound(bucket.begin(), bucket.end(), iter.key));

    Recorder recorder(N);

    std::sort(query.begin(), query.end());

    ValueVector ans(query.size());

    valueType nowTime = (valueType) modify.size() - 1, nowKey = S;

    for (auto const &iter: query) {
        while (nowKey > iter.key) {
            --nowKey;

            recorder.set(pos[nowKey]);
        }

        while (nowKey < iter.key) {
            recorder.reset(pos[nowKey]);

            ++nowKey;
        }

        while (nowTime < iter.time) {
            ++nowTime;

            Modify &mod = modify[nowTime];

            pos[mod.pre].erase(mod.pos);
            pos[mod.to].insert(mod.pos);

            if (mod.pre >= nowKey && mod.to < nowKey)
                recorder.reset(mod.pos);
            else if (mod.to >= nowKey && mod.pre < nowKey)
                recorder.set(mod.pos);

//            source[mod.pos] = mod.to;

            std::swap(mod.pre, mod.to);
        }

        while (nowTime > iter.time) {
            Modify &mod = modify[nowTime];

            pos[mod.pre].erase(mod.pos);
            pos[mod.to].insert(mod.pos);

            if (mod.pre >= nowKey && mod.to < nowKey)
                recorder.reset(mod.pos);
            else if (mod.to >= nowKey && mod.pre < nowKey)
                recorder.set(mod.pos);

//            source[mod.pos] = mod.to;

            std::swap(mod.pre, mod.to);

            --nowTime;
        }

        ans[iter.id] = recorder.get();
    }

    for (auto const &iter: ans)
        std::cout << iter << '\n';

    std::cout << std::flush;

    return 0;
}