【BZOJ3110】[ZJOI2013]K大數查詢(整體二分)
題目:
分析:
整體二分模板題……
先明確一下題意:每個位置可以存放多個數,第一種操作是“加入 (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();
}