P1110 [ZJOI2007] 报表统计

 

题目描述

小 Q 的妈妈是一个出纳,经常需要做一些统计报表的工作。今天是妈妈的生日,小 Q 希望可以帮妈妈分担一些工作,作为她的生日礼物之一。

经过仔细观察,小 Q 发现统计一张报表实际上是维护一个非负整数数列,并且进行一些查询操作。

在最开始的时候,有一个长度为 nn 的整数序列 aa,并且有以下三种操作:

  • INSERT i k:在原数列的第 ii 个元素后面添加一个新元素 kk;如果原数列的第 ii 个元素已经添加了若干元素,则添加在这些元素的最后(见样例说明)。
  • MIN_GAP:查询相邻两个元素的之间差值(绝对值)的最小值。
  • MIN_SORT_GAP:查询所有元素中最接近的两个元素的差值(绝对值)。

于是小 Q 写了一个程序,使得程序可以自动完成这些操作,但是他发现对于一些大的报表他的程序运行得很慢,你能帮助他改进程序么?

输入格式

第一行包含两个整数,分别表示原数列的长度 nn 以及操作的次数 mm。

第二行为 nn 个整数,为初始序列,第 ii 个整数表示 a_iai​。

接下来的 mm 行,每行一个操作,即INSERT i kMIN_GAPMIN_SORT_GAP 中的一种(无多余空格或者空行)。

输出格式

对于每一个 MIN_GAP 和 MIN_SORT_GAP 命令,输出一行答案即可。

输入输出样例

输入 #1复制

3 5
5 3 1
INSERT 2 9
MIN_SORT_GAP
INSERT 2 6
MIN_GAP
MIN_SORT_GAP

输出 #1复制

2
2
1

说明/提示

样例输入输出 1 解释

一开始的序列为 \{5,3,1\}{5,3,1}。

执行操作 INSERT 2 9 将得到 \{5,3,9,1\}{5,3,9,1},此时 MIN_GAP 为 22,MIN_SORT_GAP 为 22。

再执行操作 INSERT 2 6 将得到:\{5,3, 9, 6, 1\}{5,3,9,6,1}。

注意这个时候原序列的第 22 个元素后面已经添加了一个 99,此时添加的 66 应加在 99 的后面。这个时候 MIN_GAP 为 22,MIN_SORT_GAP 为 11。


数据规模与约定

对于全部的测试点,保证 2 \le n, m \le 5\times10^52≤n,m≤5×105,1 \leq i \leq n1≤i≤n,0 \leq a_i, k \leq 5 \times 10^80≤ai​,k≤5×108。

 

前置芝士

首先你一定要会平衡树(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(n)
时间复杂度:O(nlogn)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上过不掉,好像超时了,但是在洛谷还是稳稳的过掉了。

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值