[洛谷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;
}