題解 洛谷P2286 [HNOI2004]寵物收養場
題目描述
凡凡開了一間寵物收養場。收養場提供兩種服務:收養被主人遺棄的寵物和讓新的主人領養這些寵物。
每個領養者都希望領養到自己滿意的寵物,凡凡根據領養者的要求通過他自己發明的一個特殊的公式,得出該領養者希望領養的寵物的特點值 \(a\) ( \(a\) 是一個正整數, \(a < 2^{31}\) ),而他也給每個處在收養場的寵物一個特點值。這樣他就能夠很方便的處理整個領養寵物的過程了,寵物收養場總是會有兩種情況發生:被遺棄的寵物過多或者是想要收養寵物的人太多,而寵物太少。
被遺棄的寵物過多時,假若到來一個領養者,這個領養者希望領養的寵物的特點值為 \(a\) ,那麼它將會領養一隻目前未被領養的寵物中特點值最接近 \(a\)
收養寵物的人過多,假若到來一隻被收養的寵物,那麼哪個領養者能夠領養它呢?能夠領養它的領養者,是那個希望被領養寵物的特點值最接近該寵物特點值的領養者,如果該寵物的特點值為 \(a\) ,存在兩個領養者他們希望領養寵物的特點值分別為 \(a-b\) 和 \(a+b\) ,那麼特點值為 \(a-b\) 的那個領養者將成功領養該寵物。
一個領養者領養了一個特點值為 \(a\)
你得到了一年當中,領養者和被收養寵物到來收養所的情況,請你計算所有收養了寵物的領養者的不滿意程度的總和。這一年初始時,收養所裡面既沒有寵物,也沒有領養者。
答案對 \(10^6\) 取模。
簡化版題意
給定 \(A\) 集合和 \(B\) 集合, \(n\) 次操作,每次都是以下兩種操作之一:
0 x
:如果 \(B\) 集合是空的,那麼往 \(A\) 集合插入 \(x\) 這個數,否則找到 \(B\) 集合內最接近 \(x\) 的數(假設最接近的數字是 \(k\) ),在 \(B\) 集合內刪除 \(k\)1 x
:如果 \(A\) 集合是空的,那麼往 \(B\) 集合插入 \(x\) 這個數,否則找到 \(A\) 集合內最接近 \(x\) 的數(假設最接近的數字是 \(k\) ),在 \(A\) 集合內刪除 \(k\) ,同時答案累加 \(|x-k|\) 。
Solution
思考部分
下面的都以簡化版題意進行討論。
這題其實根本沒有 省選/NOI。
看到刪除,插入,查詢最接近的數字,明顯感覺到可以平衡樹,因為最接近 \(x\) 的數字一定是 \(x\) 的前驅或者後繼。
那麼我們就可以開兩棵平衡樹,一棵維護 \(A\) 集合內的數字,另一棵維護 \(B\) 集合內的數字。
以操作 0 x
為例,我們先判斷 \(B\) 集合對應的平衡樹是否是空的,如果是空的,那就在 \(A\) 集合對應的平衡樹內插入 \(x\) ,否則在 \(B\) 集合對應的平衡樹內找到 \(x\) 的前驅和後繼,比較兩個數和 \(x\) 差的絕對值哪個更小,假設找到的更小的值是 \(num\) ,那麼答案就加上 \(|num-x|\) ,同時在 \(B\) 集合內對應的平衡樹內刪除 \(num\) 。
為了避免查詢前驅和後繼的時候 \(\mathcal{RE}\) ,我們現在兩棵平衡樹內分別先插入 \(\infty\) 和 \(-\infty\) ,然後就完了。(因為 \(\infty\) 和另一個數的差的絕對值很大,不會被考慮到,所以不用擔心 \(\infty\) 被刪掉了)。
如果還是看不懂,那就看看程式碼吧:
Treap 版
#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 = 8e4 + 5;
const int INF = 0x3f3f3f3f3f3f3f3f; //極大值
struct Treap {
struct node {
int l ,r ,size;
int dat ,val;
node (int l = 0 ,int r = 0 ,int size = 0 ,int dat = 0 ,int val = 0) :
l(l) ,r(r) ,size(size) ,dat(dat) ,val(val) {}
}t[N]; int root ,tot;
Treap() : root(0) ,tot(0) {}
inline int New(int val) {
tot++; t[tot].dat = rand(); t[tot].val = val; t[tot].size = 1;
return tot;
}
inline void update(int now) {
t[now].size = t[t[now].l].size + t[t[now].r].size + 1;
}
inline void zig(int &now) {
int q = t[now].l;
t[now].l = t[q].r; t[q].r = now; now = q;
update(now); update(t[now].r);
}
inline void zag(int &now) {
int q = t[now].r;
t[now].r = t[q].l; t[q].l = now; now = q;
update(now); update(t[now].l);
}
inline void insert(int &now ,int val) { //插入
if (now == 0) {
now = New(val);
return ;
}
if (val < t[now].val) {
insert(t[now].l ,val);
if (t[t[now].l].dat > t[now].dat) zig(now);
}
else {
insert(t[now].r ,val);
if (t[t[now].r].dat > t[now].dat) zag(now);
}
update(now);
}
inline void insert(int val) {insert(root ,val);}
inline void remove(int &now ,int val) {
if (now == 0) return ;
if (t[now].val == val) {
if (t[now].l || t[now].r) {
if (t[now].r == 0 || t[t[now].l].dat > t[t[now].r].dat)
zig(now) ,remove(t[now].r ,val);
else zag(now) ,remove(t[now].l ,val);
update(now);
}
else now = 0;
return ;
}
val < t[now].val ? remove(t[now].l ,val) : remove(t[now].r ,val);
update(now);
}
inline void remove(int val) {remove(root ,val);}
inline void build() { //預處理部分
New(-INF); New(INF); //先插入 INF 和 -INF ,這裡可以直接呼叫 New 而不是 insert 是因為只有兩個節點
root = 1; t[1].r = 2; update(root);
}
inline bool empty() {
return t[root].size == 2; //因為插入了 INF 和 -INF ,所以我們要判斷 t[root].size == 2 ,而不是 t[root].size == 0
}
inline int findpre(int val) { //查詢前驅
int ans = 1 ,p = root;
while (p) {
if (t[p].val == val) {
if (t[p].l) {
p = t[p].l;
while (t[p].r) p = t[p].r;
ans = p;
}
break;
}
if (t[p].val < val && t[p].val > t[ans].val) ans = p;
p = val < t[p].val ? t[p].l : t[p].r;
}
return t[ans].val;
}
inline int findnext(int val) { //查詢後繼
int now = root ,ans = 2;
while (now) {
if (t[now].val == val) {
if (t[now].r) {
now = t[now].r;
while (t[now].l) now = t[now].l;
ans = now;
}
break;
}
if (t[now].val > val && t[now].val < t[ans].val) ans = now;
now = val < t[now].val ? t[now].l : t[now].r;
}
return t[ans].val;
}
}People ,Pet; //People 表示 A 集合對應的平衡樹, Pet 表示 B 集合對應的平衡樹
int n ,x ,opt ,ans;
const int mod = 1e6; //模數
signed main() {
Pet.build(); People.build(); //別忘了預處理
n = read();
while (n--) {
opt = read(); x = read();
if (opt == 0) {
if (People.empty()) Pet.insert(x); //如果為空要插入
else {
int pre = People.findpre(x) ,next = People.findnext(x);
//這裡不需要判斷有沒有數字等於 x 是因為題目已經保證了不會相等
if (x - pre <= next - x) {
ans = (ans + x - pre) % mod;
People.remove(pre);
}
else {
ans = (ans + next - x) % mod;
People.remove(next);
}
//這裡不需要加上絕對值是因為 pre < x < next
}
}
else {
if (Pet.empty()) People.insert(x); //如果為空要插入
else {
int pre = Pet.findpre(x) ,next = Pet.findnext(x);
//這裡不需要判斷有沒有數字等於 x 是因為題目已經保證了不會相等
if (x - pre <= next - x) {
ans = (ans + x - pre) % mod;
Pet.remove(pre);
}
else {
ans = (ans + next - x) % mod;
Pet.remove(next);
}
}
}
}
printf("%lld\n" ,ans);
return 0;
}
可能難度這麼高是因為程式碼難寫(