1. 程式人生 > 實用技巧 >題解 洛谷P2286 [HNOI2004]寵物收養場

題解 洛谷P2286 [HNOI2004]寵物收養場

Link

題目描述

凡凡開了一間寵物收養場。收養場提供兩種服務:收養被主人遺棄的寵物和讓新的主人領養這些寵物。

每個領養者都希望領養到自己滿意的寵物,凡凡根據領養者的要求通過他自己發明的一個特殊的公式,得出該領養者希望領養的寵物的特點值 \(a\)\(a\) 是一個正整數, \(a < 2^{31}\) ),而他也給每個處在收養場的寵物一個特點值。這樣他就能夠很方便的處理整個領養寵物的過程了,寵物收養場總是會有兩種情況發生:被遺棄的寵物過多或者是想要收養寵物的人太多,而寵物太少。

被遺棄的寵物過多時,假若到來一個領養者,這個領養者希望領養的寵物的特點值為 \(a\) ,那麼它將會領養一隻目前未被領養的寵物中特點值最接近 \(a\)

的一隻寵物。(任何兩隻寵物的特點值都不可能是相同的,任何兩個領養者的希望領養寵物的特點值也不可能是一樣的)如果有兩隻滿足要求的寵物,即存在兩隻寵物他們的特點值分別為 \(a-b\)\(a+b\) ,那麼領養者將會領養特點值為 \(a-b\) 的那隻寵物。

收養寵物的人過多,假若到來一隻被收養的寵物,那麼哪個領養者能夠領養它呢?能夠領養它的領養者,是那個希望被領養寵物的特點值最接近該寵物特點值的領養者,如果該寵物的特點值為 \(a\) ,存在兩個領養者他們希望領養寵物的特點值分別為 \(a-b\)\(a+b\) ,那麼特點值為 \(a-b\) 的那個領養者將成功領養該寵物。

一個領養者領養了一個特點值為 \(a\)

的寵物,而它本身希望領養的寵物的特點值為 \(b\) ,那麼這個領養者的不滿意程度為 \(|a-b|\)

你得到了一年當中,領養者和被收養寵物到來收養所的情況,請你計算所有收養了寵物的領養者的不滿意程度的總和。這一年初始時,收養所裡面既沒有寵物,也沒有領養者。

答案對 \(10^6\) 取模。

簡化版題意

給定 \(A\) 集合和 \(B\) 集合, \(n\) 次操作,每次都是以下兩種操作之一:

  • 0 x :如果 \(B\) 集合是空的,那麼往 \(A\) 集合插入 \(x\) 這個數,否則找到 \(B\) 集合內最接近 \(x\) 的數(假設最接近的數字是 \(k\) ),在 \(B\) 集合內刪除 \(k\)
    ,同時答案累加 \(|x-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;
}

可能難度這麼高是因為程式碼難寫(