[洛谷P3332] [ZJOI2013]K大数查询

洛谷题目链接:[ZJOI2013]K大数查询

题目描述

有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

输入输出格式

输入格式:

第一行N,M接下来M行,每行形如1 a b c或2 a b c

输出格式:

输出每个询问的结果

输入输出样例

输入样例#1:

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

输出样例#1:

1
2
1

说明

【样例说明】

第一个操作 后位置 1 的数只有 1 , 位置 2 的数也只有 1 。 第二个操作 后位置 1

的数有 1 、 2 ,位置 2 的数也有 1 、 2 。 第三次询问 位置 1 到位置 1 第 2 大的数 是

1 。 第四次询问 位置 1 到位置 1 第 1 大的数是 2 。 第五次询问 位置 1 到位置 2 第 3

大的数是 1 。‍

N,M<=50000
a<=b<=N

1操作中abs(c )<=N

2操作中c<=long long

题意这么简单就不多赘述了

题解: 首先简单的树套树是不行的,因为这里涉及到了区间插入,而树套树不支持区间插入.就算是在外层树上打标记,涉及到的操作总数还是\(O(N^2)\)级别的.

据说这题如果要用树套树做可以交换权值和位置两个维度,然后再将区间插入某个权值改成某个权值插入多个位置,然后实现区间查询?反正我不太会.

其实这题可以用整体二分的思想来做.

首先二分一个权值,然后将所有小于该权值的插入操作都用线段树实现.查找某个区间的第\(k\)小也用线段树区间查询来实现,也就是查询这个区间内的和,因为只有比\(mid\)小的权值被加入了线段树中,所以如果线段树中的和小于这个询问的\(k\)的话,就需要有更多权值加入这个区间,也就是说要满足这个询问,二分的权值就要加大,否则就要减小.

同时我们需要将这个修改的状态对答案的贡献都记录下来,避免重复统计.

这道题是整体二分入门的一道好题,如果没看懂可以多想想,其实大多数博客写的内容都差不多,主要还是靠自己思考.

#include<bits/stdc++.h>
#define ll(x) (x<<1)
#define rr(x) (x<<1|1)
using namespace std;
const int N =50000+5;

int n, m, id[N], ans[N], t1[N], t2[N];

struct segment_tree{
    int l, r, size;
    long long sum, tag;
}t[N*4];

struct options{
    int opt, l, r, k;
}o[N];

inline int gi(){
    int ans = 0, f = 1; char i = getchar();
    while(i<'0' || i>'9'){ if(i == '-') f = -1; i = getchar(); }
    while(i>='0' && i<='9') ans = ans*10+i-'0', i = getchar();
    return ans * f;
}

void build(int x, int l, int r){
    t[x].l = l, t[x].r = r, t[x].size = r-l+1, t[x].tag = 0;
    if(l == r) return; int mid = (l+r>>1);
    build(ll(x), l, mid), build(rr(x), mid+1, r);
}

inline void up(int x){ t[x].sum = t[ll(x)].sum+t[rr(x)].sum; }

inline void pushdown(int x){
    if(!t[x].tag) return;
    t[ll(x)].tag += t[x].tag, t[ll(x)].sum += t[x].tag*t[ll(x)].size;
    t[rr(x)].tag += t[x].tag, t[rr(x)].sum += t[x].tag*t[rr(x)].size;
    t[x].tag = 0;
}

void update(int x, int l, int r, long long val){
    if(l <= t[x].l && t[x].r <= r){
        t[x].tag += val;
        t[x].sum += val*t[x].size;
        return;
    }
    int mid = (t[x].l+t[x].r>>1); pushdown(x);
    if(l <= mid) update(ll(x), l, r, val);
    if(mid < r) update(rr(x), l, r, val); up(x);
}

long long query(int x, int l, int r){
    if(l <= t[x].l && t[x].r <= r) return t[x].sum;
    if(t[x].r < l || r < t[x].l) return 0;
    pushdown(x);
    return query(ll(x), l, r)+query(rr(x), l, r);
}

void solve(int l, int r, int ql, int qr){
    if(ql > qr) return;
    if(l == r){
        for(int i=ql;i<=qr;i++)
            if(o[id[i]].opt == 2) ans[id[i]] = l;
        return;
    }
    int mid = (l+r>>1), cnt1 = 0, cnt2 = 0, len = ql-1;
    long long sum;
    for(int i=ql;i<=qr;i++){
        if(o[id[i]].opt == 1){
            if(o[id[i]].k > mid) t1[++cnt1] = id[i], update(1, o[id[i]].l, o[id[i]].r, 1);
            else t2[++cnt2] = id[i];
        } else {
            sum = query(1, o[id[i]].l, o[id[i]].r);
            if(sum >= o[id[i]].k) t1[++cnt1] = id[i];
            else t2[++cnt2] = id[i], o[id[i]].k -= sum;
        }
    }
    for(int i=ql;i<=qr;i++)
        if(o[id[i]].opt == 1 && o[id[i]].k > mid)
            update(1, o[id[i]].l, o[id[i]].r, -1);
    for(int i=1;i<=cnt2;i++) id[++len] = t2[i];
    for(int i=1;i<=cnt1;i++) id[++len] = t1[i];
    solve(l, mid, ql, ql+cnt2-1), solve(mid+1, r, ql+cnt2, qr);
}

int main(){
    //freopen("data.in", "r", stdin);
    n = gi(), m = gi(), build(1, 1, n);
    for(int i=1;i<=m;i++)
        o[i].opt = gi(), o[i].l = gi(), o[i].r = gi(), o[i].k = gi(), id[i] = i;
    solve(-1e9, 1e18, 1, m);
    for(int i=1;i<=m;i++)
	if(o[i].opt == 2) printf("%d\n", ans[i]);
    return 0;
}
posted @ 2018-09-10 20:08  Brave_Cattle  阅读(377)  评论(0编辑  收藏  举报