1. 程式人生 > 實用技巧 >題解 洛谷P1486 [NOI2004]鬱悶的出納員

題解 洛谷P1486 [NOI2004]鬱悶的出納員

題目簡述

Link

您需要高效的維護一個集合,支援一下操作:

  • 插入一個數 \(x\)
  • 將集合裡面的所有數字加上/減去 \(k\) ,同時刪除所有小於 \(\min\) 的數。
  • 查詢集合第 \(k\) 大。

Solution

首先,看到查詢第 \(k\) 大,自然想到使用平衡樹,但是這裡有加法和減法,並且還有集體刪除,但我們在題目裡面發現一個有趣的東西:

  • AS 命令的總條數不超過 \(100\)

那麼,也就是說,我們加法的時候,直接暴力加上去就可以了,比如:

for (int i = 1;i <= tot; i++) t[i].val += val;

然後來看看怎麼刪除所有小於 \(\min\)

的數,以 FHQ-Treap 為例,我們可以把平衡樹分裂,是的其中一部分的權值小於或等於 \(\min - 1\) ,然後直接丟掉那棵樹就可以了。

沒錯,省選/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......