[luogu1110][ZJOI2007]报表统计【平衡树】

时间:2022-04-01 21:07:25

传送门

【洛谷传送门】
【bzoj传送门】

前言

洛谷和网上的题解都好复杂哦,或者是stl水过。
窝的语文不怎么好,所以会有一些表达上的累赘或者是含糊不清,望各大佬海涵。


前置芝士

首先你一定要会平衡树(BST)。
什么平衡树都可以,只要是能过掉【模板】普通平衡树的都可以。
关于平衡树的详细操作这里就不一一赘述了。


正解

这一道题目看到的时候不能盲目思考能不能用一个数据结构一下子维护所有的正确答案,对于这一道题目是很难实现的。反正蒟蒻是实现不了
我们将这个问题一层一层的剖析一下。

第一个操作

插入操作,因为是在每一个原来的数后面插入一个数。
那么很容易想到用不定长数组vector来实现。
因为这个满足vector的结尾插入和不妨碍其他数组的性质。

第二个操作

查询相邻两个元素之间的差值(绝对值)的最小值。
比较容易想到,如果我们已经处理好了之前的答案,那么只有在插入的时候会影响这个答案。这句话很重要。
而且改变的肯定是一个vector的队尾和下一个vector的队首之间的答案。
那么我们就将所有队尾和下一个vector的队首的差值维护一下。
但是有修改的操作,所以我们每一次插入一个数,那么会造成以下两种情况:
我们假设我们的数组是v[n][n]vector,当前我们插入的数是第x个数组,权值为Val。每一个vector的结尾是v[x].tail,每一个vector的头是v[x].head

  • 减少一个答案,也就是v[x].tailv[x+1].head之间的答案
  • 增加两个答案,也就是v[x].tailkv[x+1].headk之间的答案。

反观上面两种情况,因为我们是维护最小值,那么可以用可修改的堆来维护,但是我比较弱,又比较懒,就写了一个带插入和删除的Treap,然后我们每一次需要输出答案的时候,只需要找到这个Treap中的最小值,也就是一直往左儿子走就可以了。

第三个操作

这个操作就比较裸了,因为我们知道和当前这个数要差值最小,那么只有可能是他的前驱和后继。
这个又是平衡树可以实现的操作,那么我们只需要一个可以实现查找前驱和后继的BST就可以了。窝还是用了Treap来实现。

ps.需要注意下,因为我们第三个操作只是在修改的时候有操作,如果没有插入操作前,我们这个做法就是错的,那么就预处理排序一下,然后得到最小的差值就可以了

总结一下

  • 操作1:vector暴力加入
  • 操作2:treap维护差值
  • 操作3:treap维护数值

复杂度分析

空间复杂度:\(O(n)\)
时间复杂度:\(O(nlogn)\)
代码复杂度:比较低。
思维难度:比较低。


代码

#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define inf 0x3f3f3f3f
#define pb push_back
#define N 1000005
using namespace std;
template <typename T>
inline void read(T &x) {//快读不说
    x = 0; T fl = 1; char ch = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        if (ch == '-') fl = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        x = (x << 1) + (x << 3) + (ch ^ 48);
    x *= fl;
}
vector<int> v[N];
map<int, int> mp;
struct Treap {//treap模板,有需要可以copy
    int tot, rt;
    struct node {
        int val, ch[2], rd, cnt, sz;
        void Init(int Val) { val = Val, rd = rand() % 233; sz = cnt = 1; ch[1] = ch[0] = 0; }
    }tr[N];
    void pushup(int nod) { tr[nod].sz = tr[tr[nod].ch[0]].sz + tr[tr[nod].ch[1]].sz + tr[nod].cnt; }
    void rotate(int &nod, int d) {
        int k = tr[nod].ch[d]; tr[nod].ch[d] = tr[k].ch[d ^ 1]; tr[k].ch[d ^ 1] = nod;
        pushup(nod); pushup(k); nod = k;
    }
    void ins(int &nod, int val) {
        if (!nod) { nod = ++ tot; tr[nod].Init(val); }
        else {
            tr[nod].sz ++;
            if (tr[nod].val == val) { tr[nod].cnt ++; return; }
            int d = val > tr[nod].val;
            ins(tr[nod].ch[d], val);
            if (tr[nod].rd > tr[tr[nod].ch[d]].rd) rotate(nod, d);
        }
    }
    void del(int &nod, int val) {
        if (!nod) return;
        if (tr[nod].val == val) {
            if (tr[nod].cnt > 1) { tr[nod].cnt --, tr[nod].sz --; return; }
            int d = tr[tr[nod].ch[0]].rd > tr[tr[nod].ch[1]].rd;
            if (!tr[nod].ch[1] || !tr[nod].ch[0]) nod = tr[nod].ch[1] + tr[nod].ch[0];
            else rotate(nod, d), del(nod, val);
        }
        else tr[nod].sz --, del(tr[nod].ch[tr[nod].val < val], val);
    }
    int pre(int nod, int val) {
        if (!nod) return -inf;
        if (tr[nod].val > val) return pre(tr[nod].ch[0], val);
        else return max(tr[nod].val, pre(tr[nod].ch[1], val));
    }
    int suc(int nod, int val) {
        if (!nod) return inf;
        if (tr[nod].val < val) return suc(tr[nod].ch[1], val);
        else return min(tr[nod].val, suc(tr[nod].ch[0], val));
    }
    int Get_Min(int nod) {
        if (!nod) return inf;
        return min(tr[nod].val, Get_Min(tr[nod].ch[0]));
    }
}tp, tp2;//两个treap
int ans, n, m;
char opt[5];
int a[N];
int main() {
    srand(19260817);//随机种子
    read(n); read(m); ans = inf;
    for (int i = 1; i <= n; i ++) { v[i].clear(); read(a[i]); v[i].pb(a[i]); tp.ins(tp.rt, a[i]); }
    for (int i = 2; i <= n; i ++) tp2.ins(tp2.rt, abs(v[i][0] - v[i - 1][0]));
    sort(a + 1, a + 1 + n); for (int i = 2; i <= n; i ++) ans = min(ans, a[i] - a[i - 1]);//预处理出操作3的答案
    for (int i = 1; i <= m; i ++) {
        scanf("%s", opt);
        if (opt[0] == 'I') {
            int x, k; read(x); read(k);
            int lst = tp.pre(tp.rt, k), nxt = tp.suc(tp.rt, k);
            tp.ins(tp.rt, k); //插入当前的数
            ans = min(ans, min(abs(lst - k), abs(nxt - k)));//查找前驱和后继来更新答案
            tp2.del(tp2.rt, abs(v[x][(int)v[x].size() - 1] - v[x + 1][0])); //删除原先答案
            tp2.ins(tp2.rt, abs(v[x][(int)v[x].size() - 1] - k));
            tp2.ins(tp2.rt, abs(k - v[x + 1][0])); //加入现在答案
            v[x].pb(k); //插入这个数
        }
        if (opt[4] == 'G') printf("%d\n", tp2.Get_Min(tp2.rt));//查找最小差值
        if (opt[4] == 'S') printf("%d\n", ans);
    }
    return 0;
}

总结一下

这里我并没有把sort看成stl因为太习惯了。
我觉得吧,我并不是对stl有偏见,虽然stl在考场的时候可以救命,但是在平时的训练我还是尽可能的少用stl,像平衡树这种东西,多敲敲除了费时间,对于自己的代码能力还是有帮助的。
自己层层剖析问题的能力还是需要加强。(我果然是一个蒟蒻)
这个程序在bzoj上过不掉,好像超时了,但是在洛谷还是稳稳的过掉了。