[luogu1110][ZJOI2007]報表統計【平衡樹】
傳送門
【洛谷傳送門】
【bzoj傳送門】
前言
洛谷和網上的題解都好復雜哦,或者是stl
水過。
窩的語文不怎麽好,所以會有一些表達上的累贅或者是含糊不清,望各大佬海涵。
前置芝士
首先你一定要會平衡樹(BST)。
什麽平衡樹都可以,只要是能過掉【模板】普通平衡樹的都可以。
關於平衡樹的詳細操作這裏就不一一贅述了。
正解
這一道題目看到的時候不能盲目思考能不能用一個數據結構一下子維護所有的正確答案,對於這一道題目是很難實現的。反正蒟蒻是實現不了
我們將這個問題一層一層的剖析一下。
第一個操作
插入操作,因為是在每一個原來的數後面插入一個數。
那麽很容易想到用不定長數組vector
來實現。
因為這個滿足vector
第二個操作
查詢相鄰兩個元素之間的差值(絕對值)的最小值。
比較容易想到,如果我們已經處理好了之前的答案,那麽只有在插入的時候會影響這個答案。這句話很重要。
而且改變的肯定是一個vector
的隊尾和下一個vector
的隊首之間的答案。
那麽我們就將所有隊尾和下一個vector
的隊首的差值維護一下。
但是有修改的操作,所以我們每一次插入一個數,那麽會造成以下兩種情況:
我們假設我們的數組是v[n][n]
的vector
,當前我們插入的數是第x
個數組,權值為Val
。每一個vector
的結尾是v[x].tail
,每一個vector
的頭是v[x].head
。
- 減少一個答案,也就是
v[x].tail
v[x+1].head
之間的答案
- 增加兩個答案,也就是
v[x].tail
和k
和v[x+1].head
和k
之間的答案。
反觀上面兩種情況,因為我們是維護最小值,那麽可以用可修改的堆來維護,但是我比較弱,又比較懶,就寫了一個帶插入和刪除的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
上過不掉,好像超時了,但是在洛谷還是穩穩的過掉了。
[luogu1110][ZJOI2007]報表統計【平衡樹】