洛谷P4632 [APIO2018] New Home 新家(動態開節點線段樹 二分答案 掃描線 set)

自為風月馬前卒發表於2018-10-22

題意

題目連結

Sol

這題沒有想象中的那麼難,但也絕對不簡單。

首先把所有的詢問離線,按照出現的順序。維護時間軸來處理每個詢問

對於每個詢問((x_i, y_i)),可以二分答案(mid)

問題轉化為對於所有(a_i leqslant y_i leqslant b_i)的商店,((x – mid, x + mid))內是否所有型別的商店都出現過

若都出現過,減小(mid),否則增大(mid)

現在有兩個問題:

  1. 如何維護當前可行的所有商店,以及我們需要的資訊

  2. 如何判斷((x – mid, x + mid))內是否所有型別的商店都出現過

顯然問題1依賴於問題2

對於第二個問題,一種方法是直接樹套樹區間數顏色,另一個巧妙的方法是定義(pre_i)表示和(i)號位置型別相同的商店中,(x)座標小於(i)的第一個位置

(mid)是合法的,一定存在一個位置(p in (x + mid + 1, N))滿足(pre_p < x – mid)

那麼直接用線段樹維護(pre)的最小值即可。

線段樹應該動態開點,當然你也可以頭鐵寫離散化然後就需要考慮各種邊界問題。。

問題1實際上我們只需要維護好(pre)即可

一個顯然的想法是直接開(30w)個set維護每個型別

加入 / 刪除的時候只會影響到(3)個位置

時間複雜度:(O(nlog^2n)),單次詢問的複雜度為(O(logn))

需要注意一個細節,由於是線上段樹上二分,所以二分的邊界應該與線段樹相同

#include<bits/stdc++.h>
#define mit multiset<int>::iterator 
using namespace std;
const int MAXN = 3e5 + 10, L = 1e9, Lim = (1 << 22) + 1;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < `0` || c > `9`) {if(c == `-`) f = -1; c = getchar();}
    while(c >= `0` && c <= `9`) x = x * 10 + c - `0`, c = getchar();
    return x * f;
}
int N, K, Q, cnt, rt, ans[MAXN];
multiset<int> op[MAXN];//val of each type
multiset<int> s[Lim];//
int Mn[Lim], ls[Lim], rs[Lim];
struct Query {
    int ti, opt, x;// time type pos
    bool operator < (const Query &rhs) const {
        return ti == rhs.ti ? opt < rhs.opt : ti < rhs.ti;
    }
}q[MAXN * 3 + 10];
int tot = 0;
void Erase(multiset<int> &s, int &val) {
    mit t = s.find(val);
    if(t != s.end()) s.erase(t);
}
void update(int k) {
    Mn[k] = min(Mn[ls[k]], Mn[rs[k]]);
}
void Insert(int &k, int l, int r, int x, int New, int Old) {
    if(!k) k = ++tot;
    if(l == r) {
        if(New) s[k].insert(New);
        if(Old) Erase(s[k], Old);
        
        Mn[k] = (s[k].empty() ? L :  *s[k].begin()); return ;
    }
    int mid = l + r >> 1;
    if(x <= mid) Insert(ls[k], l, mid, x, New, Old);
    else         Insert(rs[k], mid + 1, r, x, New, Old);
    update(k);
}
int query(int x) {
    int l = 0, r = L, mid, now = rt, lim = L, ans;
    while(l < r) {
        int mid = l + r >> 1, mn = min(Mn[rs[now]], lim);
        if((x > mid) || (mid - x < x - mn)) 
            l = mid + 1, now = rs[now], ans = mid;
        else r = mid, now = ls[now], lim = mn;
    }
    return l - x;
}

    N = read(); K = read(); Q = read(); Mn[0] = L;
    for(int i = 1; i <= N; i++) {
        int x = read(), t = read(), a = read(), b = read();
        q[++cnt] = (Query) {a, t, x};
        q[++cnt] = (Query) {b + 1, -t, x};
    }
    for(int i = 1; i <= Q; i++) {
        int x = read(), y = read();
        q[++cnt] = (Query) {y, N + i, x};
    }
    sort(q + 1, q + cnt + 1); int Now = 0;// now type num
    for(int i = 1; i <= K; i++) op[i].insert(L), op[i].insert(-L), Insert(rt, 0, L, L, -L, 0);
    for(int i = 1; i <= cnt; i++) {
        int ty = q[i].opt;
        if(ty > N)
            ans[ty - N] = Now < K ? - 1 : query(q[i].x);
        else if(ty > 0) { // add
            mit t = op[ty].upper_bound(q[i].x), r = t--;
            Insert(rt, 0, L, *r, q[i].x, *t);
            Insert(rt, 0, L, q[i].x, *t, 0);
            if(op[ty].size() == 2) Now++;
            op[ty].insert(q[i].x);

        } else {
            ty = -ty;
            mit t = op[ty].upper_bound(q[i].x), r = t--; t--;
            Insert(rt, 0, L, *r, *t, q[i].x);
            Insert(rt, 0, L, q[i].x, 0, *t);
            Erase(op[ty], q[i].x);
            if(op[ty].size() == 2) Now--;
        }
    }
    for(int i = 1; i <= Q; i++) printf("%d
", ans[i]);
    return 0;
}

相關文章