題解 洛谷P1486 [NOI2004]鬱悶的出納員
阿新 • • 發佈:2020-12-22
題目簡述
您需要高效的維護一個集合,支援一下操作:
- 插入一個數 \(x\) 。
- 將集合裡面的所有數字加上/減去 \(k\) ,同時刪除所有小於 \(\min\) 的數。
- 查詢集合第 \(k\) 大。
Solution
首先,看到查詢第 \(k\) 大,自然想到使用平衡樹,但是這裡有加法和減法,並且還有集體刪除,但我們在題目裡面發現一個有趣的東西:
A
和S
命令的總條數不超過 \(100\)
那麼,也就是說,我們加法的時候,直接暴力加上去就可以了,比如:
for (int i = 1;i <= tot; i++) t[i].val += val;
然後來看看怎麼刪除所有小於 \(\min\)
沒錯,省選/NOI-就是這麼簡單。
但是程式碼有一些細節需要注意:
- 查詢集合第 \(k\) 大,而不是第 \(k\) 小,因此我們需要查詢區間第 \(t[root].size - k + 1\) 小的數字( \(t[root].size\) 表示集合中數的總個數。
- 並且,我們還要先判斷 \(k\) 是不是大於 \(t[root].size\) 。
- 插入的時候如果當前的數字小於 \(\min\) 是不算離開公司的人的 ,也就是說,我們只需要記錄 FHQ-Treap 分裂的時候分裂出的子樹大小就行了。
可能會爆 int
,所以我開了 long long
。
程式碼如下:
#include <cstdio> #include <cstring> #include <cctype> #include <algorithm> #define int long long using std::rand; inline int read() { int num = 0 ,f = 1; char c = getchar(); while (!isdigit(c)) f = c == '-' ? -1 : 1 ,c = getchar(); while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar(); return num * f; } const int N = 3e5 + 5; const int INF = 0x3f3f3f3f3f3f3f3f; struct node { int ch[2]; int val ,dat ,size; node (int l = 0 ,int r = 0 ,int val = 0 ,int dat = 0 ,int size = 0) : val(val) ,dat(dat) ,size(size) { ch[0] = l; ch[1] = r; } }t[N]; int root ,tot; inline int New(int val) { tot++; t[tot].val = val; t[tot].dat = rand(); t[tot].size = 1; return tot; } inline void update(int now) {t[now].size = t[t[now].ch[0]].size + t[t[now].ch[1]].size + 1;} inline void split(int now ,int val ,int &x ,int &y) { if (now == 0) { x = y = 0; return ; } if (t[now].val <= val) { x = now; split(t[now].ch[1] ,val ,t[now].ch[1] ,y); } else { y = now; split(t[now].ch[0] ,val ,x ,t[now].ch[0]); } update(now); } inline int merge(int x ,int y) { if (x == 0 || y == 0) return x + y; if (t[x].dat < t[y].dat) { t[x].ch[1] = merge(t[x].ch[1] ,y); update(x); return x; } else { t[y].ch[0] = merge(x ,t[y].ch[0]); update(y); return y; } } inline void insert(int val) { int x ,y; split(root ,val ,x ,y); root = merge(merge(x ,New(val)) ,y); } inline int getval(int now ,int rank) { if (t[now].size < rank) return 0; if (now == 0) return 0; while (true) { int v = t[now].ch[0]; if (t[v].size >= rank) now = v; else if (t[v].size + 1 >= rank) return now; else { rank -= t[v].size + 1; now = t[now].ch[1]; } } } inline void add(int val) { //整體加 for (int i = 1;i <= tot; i++) t[i].val += val; } int m ,minn ,ans; inline void reduce(int val) { //整體減 for (int i = 1;i <= tot; i++) t[i].val -= val; int x; split(root ,minn - 1 ,x ,root); //減法才可能造成成員離開公司,所以我們要分裂 ans += t[x].size; //記得更新答案。 } char opt[5]; int x; signed main() { m = read(); minn = read(); while (m--) { scanf("%s" ,opt); x = read(); if (opt[0] == 'I') { if (x < minn) continue; insert(x); } else if (opt[0] == 'A') add(x); else if (opt[0] == 'S') reduce(x); else if (opt[0] == 'F') { if (x > t[root].size) puts("-1"); else printf("%lld\n" ,t[getval(root ,t[root].size - x + 1)].val); } } printf("%lld\n" ,ans); return 0; }
一開始寫 Splay 實在調不出來我才寫了 FHQ-Treap emmmm......