P3960 [NOIP2017 提高组] 列队

题解(参考董晓的写法)


题目链接

P3960 [NOIP2017 提高组] 列队

解题思路

树状数组 + 二分(也可动态开点线段树等)
离线,对每一行,以及最后一列开一个数组存后加值(通过看齐操作而到我这行/列的值),再分别使用树状数组+二分求出每次查询的横(纵)坐标的实际值,然后查询求解。

时间复杂度与空间复杂度分析

树状数组大小 \(mx = n + q\),二分范围 \(0-mx\)\(mx\)次查询,最终复杂度约为 \(O((n + q)log(n + q))\)

完整代码

#include <bits/stdc++.h>

#define int long long
#define lowbit(x) (x & -x)

using namespace std;

struct BIT {
    int n;
    vector<int> s;

    BIT(int n) : n(n) {
        s.assign(n + 1, {});
    }  

    void update(int x, int k) {
        for (int i = x; i <= n; i += lowbit(i)) {
            s[i] += k;     
        }
    }

    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) {
            res += s[i];
        }
        return res;
    }
    //以上均为树状数组模板

    int binarySearch(int x) {   //结合树状数组二分查找给定列(行)x的真实列(行)值
        int l = 0, r = n;       //原理:对于理应删除的一个节点,不实际删除,而是将其位置元素值变为0,再结合树状数组存储前缀和的性质可以查询真实值
        while (l < r) {
            int mid = l + r >> 1;
            if (query(mid) >= x) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

struct Q {
    int y, id;
};

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);

    int n, m, T;
    cin >> n >> m >> T;
    int mx = max(n, m) + T;     //最大的可能的树状数组大小

    vector<int> x(T + 1), y(T + 1);
    vector<vector<Q>> v(n + 1);
    for (int i = 1; i <= T; ++i) {
        cin >> x[i] >> y[i];
        if (y[i] != m) v[x[i]].push_back({y[i], i});    //非最后一列的查询元素放入每行的树状数组中
    }

    BIT bit(mx);
    vector<int> col(T + 1); //(一个树状数组循环利用,当作n行的树状数组)
    for (int i = 1; i <= mx; ++i) bit.update(i, 1);
    for (int i = 1; i <= n; ++i) {
        for (auto [y, id] : v[i]) {
            col[id] = bit.binarySearch(y);  //查询第id个查询真实的列下标
            bit.update(col[id], -1);        //当前行修改过的元素的 列位置 设置为空
        }
        for (auto [y, id] : v[i]) {
            bit.update(col[id], 1);         //还原树状数组,继续用于下一行
        }
    }

    vector<vector<int>> q(n + 1);
    for (int i = 1; i <= T; ++i) {
        int row = bit.binarySearch(x[i]);   //row: 当前行的末尾(第m列)的实际值所在行
        int tail = 0, ans = 0;              //tail: 当前行末尾实际值
        bit.update(row, -1);                //同 列的修改

        //分类取出真实tail值
        if (row <= n) tail = row * m;       //tail取自原始的真实行尾值,可直接根据数组下标计算
        else tail = q[0][row - n - 1];      //tail取自后面插入的行尾值

        //分类取出真实答案ans
        if (y[i] == m) ans = tail;          //修改的正好是行尾值
        else {                              //修改的不是行尾值
            q[x[i]].push_back(tail);        //不是行尾值的话,因为行尾值会进入当前行,不再是行尾值,所以需要将行尾值插入当前行的后插入数组中
            if (col[i] < m) ans = (x[i] - 1) * m + col[i];  //ans取自原始的真实行内值
            else ans = q[x[i]][col[i] - m];                 //ans取自当前行的后插入数组中
        }
        q[0].push_back(ans);                //ans需要插入最后一列的后插入数组中
        cout << ans << "\n";
    }

    return 0;
}

AC提交记录

(https://www.luogu.com.cn/record/176544896)
image

posted @ 2024-10-28 21:56  medicos  阅读(43)  评论(0)    收藏  举报