1. 程式人生 > >【BZOJ3110】[ZJOI2013]K大數查詢(整體二分)

【BZOJ3110】[ZJOI2013]K大數查詢(整體二分)

題目:

BZOJ3110

分析:

整體二分模板題……

先明確一下題意:每個位置可以存放多個數,第一種操作是“加入 (insert) ”一個數而不是“加上 (add) ”一個數。

首先考慮只有一次詢問的情況。設詢問的名次為\(k\),我們二分出一個答案\(mid\),然後遍歷所有修改。建立一棵區間線段樹(下標是位置的線段樹),對於一個給\([a,b]\)區間加入一個數\(c\)的修改,如果\(c\geq mid\),就給\([a,b]\)這個區間整體加\(1\)。最後查詢詢問區間的和,即這個區間中不小於\(mid\)的數的數量。如果這個數量大於等於\(k\)則向上二分,並記錄答案,否則向下二分。這樣單次詢問的複雜度是\(O(mlog_2^2n)\)

可以看出,詢問時最耗時間的是遍歷所有修改並維護線段樹。而這個操作只與當前二分到的\(mid\)有關,與詢問無關。因此考慮把所有詢問放在一個集合中,每次把詢問分為將要“向上二分”(答案在\([mid+1,r]\))和“向下二分”(答案在\([l,mid]\)兩個集合,並把可能對它們產生貢獻的修改也分別加入這兩個集合,然後分別遞迴下去。這就是“整體二分”。下面重點介紹如何對詢問和修改分為兩個集合。以下把修改和詢問統稱為“操作”。

詢問的分法比較顯然。如同只有一個詢問的情況,把當前操作集合中的修改全部插入到線段樹上。如果詢問區間的和大於等於詢問的名次則把這個詢問分到“向上二分”的集合中,否則分到“向下二分”的集合中。

考慮修改。如果一個修改所加入的數不大於\(mid\),那麼對於已經認定答案在\([mid+1,r]\)的詢問一定是沒有貢獻的,所以只需要加到向下二分的集合中;如果一個修改所加入的數大於\(mid\),那麼對於已經認定答案在\([l,mid]\)的詢問一定是有\(1\)的貢獻。如果把答案在\([l,mid]\)的詢問所求的名次都減去\(1\),則這個修改也只會對向上二分的集合中的詢問有貢獻,只需要加到向上二分的集合中。這樣每次都把所有操作分成獨立的兩部分,最多分\(log_2n\)次。每層所有操作集合的並集剛好是原集合,所以每層的時間複雜度是\(nlog_2n\),總複雜度\(O(nlog_2^2n)\)

。具體流程可以參考程式碼。

程式碼:

並不需要真正把操作分成兩個集合,只分它們的編號即可。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
using namespace std;

namespace zyt
{
    template<typename T>
    inline void read(T &x)
    {
        char c;
        bool f = false;
        x = 0;
        do
            c = getchar();
        while (c != '-' && !isdigit(c));
        if (c == '-')
            f = true, c = getchar();
        do
            x = x * 10 + c - '0', c = getchar();
        while (isdigit(c));
        if (f)
            x = -x;
    }
    template<typename T>
    inline void write(T x)
    {
        static char buf[20];
        char *pos = buf;
        if (x < 0)
            putchar('-'), x = -x;
        do
            *pos++ = x % 10 + '0';
        while (x /= 10);
        while (pos > buf)
            putchar(*--pos);
    }
    typedef long long ll;
    const int N = 5e4 + 10, B = 16, CHANGE = 1, QUERY = 2;
    int n, m, id[N], ans[N];
    struct node
    {
        int type, l, r;
        ll c;
    }opt[N];
    namespace Segment_Tree
    {
        struct node
        {
            ll sum, tag;
        }tree[1 << (B + 1)];
        inline void update(const int rot)
        {
            tree[rot].sum = tree[rot << 1].sum + tree[rot << 1 | 1].sum;
        }
        inline void push_down(const int rot, const int lt, const int rt)
        {
            if (tree[rot].tag)
            {
                ll &tag = tree[rot].tag;
                int mid = (lt + rt) >> 1;
                tree[rot << 1].sum += tag * (mid - lt + 1);
                tree[rot << 1].tag += tag;
                tree[rot << 1 | 1].sum += tag * (rt - mid);
                tree[rot << 1 | 1].tag += tag;
                tag = 0;
            }
        }
        void add(const int rot, const int lt, const int rt, const int ls, const int rs, const int x)
        {
            if (ls <= lt && rt <= rs)
            {
                tree[rot].sum += x * (rt - lt + 1), tree[rot].tag += x;
                return;
            }
            int mid = (lt + rt) >> 1;
            push_down(rot, lt, rt);
            if (ls <= mid)
                add(rot << 1, lt, mid, ls, rs, x);
            if (rs > mid)
                add(rot << 1 | 1, mid + 1, rt, ls, rs, x);
            update(rot);
        }
        ll query(const int rot, const int lt, const int rt, const int ls, const int rs)
        {
            if (ls <= lt && rt <= rs)
                return tree[rot].sum;
            int mid = (lt + rt) >> 1;
            ll ans = 0;
            push_down(rot, lt, rt);
            if (ls <= mid)
                ans += query(rot << 1, lt, mid, ls, rs);
            if (rs > mid)
                ans += query(rot << 1 | 1, mid + 1, rt, ls, rs);
            return ans;
        }
    }
    void solve(const int idl, const int idr, const int l, const int r)
    {//As for any question, determine
    //wheather there are more than opt[i].c numbers greater than mid or not
    //If so, ans[i] will be greater than mid. Otherwise less.
        using Segment_Tree::query;
        using Segment_Tree::add;
        static int newl[N], newr[N];
        if (l == r)
        {
            for (int i = idl; i <= idr; i++)
                if (opt[id[i]].type == QUERY)
                    ans[id[i]] = l;
            return;
        }
        int lcnt = 0, rcnt = 0;
        int mid = (l + r) >> 1;
        for (int i = idl; i <= idr; i++)
        {
            if (opt[id[i]].type == CHANGE)
            {
                if (opt[id[i]].c <= mid)
                    newl[lcnt++] = id[i];
                else
                {
                    add(1, 1, n, opt[id[i]].l, opt[id[i]].r, 1);
                    query(1, 1, n, 100, 100);
                    newr[rcnt++] = id[i];
                }
            }
            else
            {
                ll tmp = query(1, 1, n, opt[id[i]].l, opt[id[i]].r);
                if (tmp < opt[id[i]].c)
                    newl[lcnt++] = id[i], opt[id[i]].c -= tmp;
                else
                    newr[rcnt++] = id[i];
            }
        }
        for (int i = idl; i <= idr; i++)
            if (opt[id[i]].type == CHANGE && opt[id[i]].c > mid)
                add(1, 1, n, opt[id[i]].l, opt[id[i]].r, -1);
        memcpy(id + idl, newl, sizeof(int[lcnt]));
        memcpy(id + idl + lcnt, newr, sizeof(int[rcnt]));
        if (lcnt)
            solve(idl, idl + lcnt - 1, l, mid);
        if (rcnt)
            solve(idl + lcnt, idr, mid + 1, r);
    }
    int work()
    {
        read(n), read(m);
        for (int i = 1; i <= m; i++)
        {
            read(opt[i].type);
            read(opt[i].l);
            read(opt[i].r);
            read(opt[i].c);
            id[i] = i;
        }
        solve(1, m, -N, N);
        for (int i = 1; i <= m; i++)
            if (opt[i].type == QUERY)
                write(ans[i]), putchar('\n');
        return 0;
    }
}
int main()
{
    freopen("3110.in", "r", stdin);
    freopen("3110.out", "w", stdout);
    return zyt::work();
}