BZOJ3110[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
输出
输出每个询问的结果
样例输入
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 2
2 1 1 2
2 1 1 1
2 1 2 3
样例输出
1
2
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,N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint
显然一棵线段树或平衡树无法维护这道题的操作,那么考虑树套树。
一开始可能会想将维护序列的线段树放在外层,然后将维护序列每个点各种权值的权值线段树放在内层。
这样修改操作就是外层线段树区间修改,内层线段树单点修改,很好操作。
但你发现无法完成询问操作,如果用二分答案来解决的话时间复杂度就是O(nlogn^3)的了。
既然询问第k大,那么显然权值线段树对于这种操作更为擅长,我们将权值线段树放在外层,将维护序列的线段树放在内层。
这样修改就变成了外层线段树单点修改,内层线段树区间修改,同样很好操作。
对于询问,我们每查询到权值线段树的一个节点先在这个点左子节点的内层线段树上区间查找,即找出序列上询问区间中权值小于某个值的数量,再与k判断来决定往左走还是往右走。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<bitset> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int n,m; int opt; int cnt; int a,b; ll c; ll s[20000010]; int root[2000010]; ll sum[20000010]; int ls[20000010]; int rs[20000010]; inline void pushup(int rt) { sum[rt]=sum[ls[rt]]+sum[rs[rt]]; } inline void pushdown(int rt,int l,int r) { if(s[rt]) { int mid=(l+r)>>1; if(!ls[rt]) { ls[rt]=++cnt; } if(!rs[rt]) { rs[rt]=++cnt; } s[ls[rt]]+=s[rt]; s[rs[rt]]+=s[rt]; sum[ls[rt]]+=s[rt]*(mid-l+1); sum[rs[rt]]+=s[rt]*(r-mid); s[rt]=0; } } inline void change(int &rt,int l,int r,int L,int R) { if(!rt) { rt=++cnt; } if(L<=l&&r<=R) { sum[rt]+=(r-l+1); s[rt]++; return ; } pushdown(rt,l,r); int mid=(l+r)>>1; if(L<=mid) { change(ls[rt],l,mid,L,R); } if(R>mid) { change(rs[rt],mid+1,r,L,R); } pushup(rt); } inline ll find(int rt,int l,int r,int L,int R) { if(!rt) { return 0; } if(L<=l&&r<=R) { return sum[rt]; } pushdown(rt,l,r); int mid=(l+r)>>1; ll res=0; if(L<=mid) { res+=find(ls[rt],l,mid,L,R); } if(R>mid) { res+=find(rs[rt],mid+1,r,L,R); } return res; } inline void insert(int rt,int l,int r,int k,int L,int R) { change(root[rt],1,n,L,R); if(l==r) { return ; } int mid=(l+r)>>1; if(k<=mid) { insert(rt<<1,l,mid,k,L,R); } else { insert(rt<<1|1,mid+1,r,k,L,R); } } inline int query(int rt,int l,int r,ll k,int L,int R) { if(l==r) { return l; } int mid=(l+r)>>1; ll res=find(root[rt<<1|1],1,n,L,R); if(res<k) { return query(rt<<1,l,mid,k-res,L,R); } else { return query(rt<<1|1,mid+1,r,k,L,R); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d",&opt); scanf("%d%d%lld",&a,&b,&c); if(a>b) { swap(a,b); } if(opt==1) { c=(int)c; insert(1,0,2*n,c+n,a,b); } else { printf("%d\n",query(1,0,2*n,c,a,b)-n); } } }