BZOJ 3110 / ZJOI 2013
题意
有n个位置和m个操作,操作分两种:
输入1 a b c:在a到b的所有位置加入一个数c;
输入2 a b c:询问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
2
1
SOL
首先是离散化,注意本题原始数据并没有负数并且c的范围很小(并非如题所说maxlongint)所以不用离散直接做就行,但是bzoj上加强了数据,需要离散化,并且有可能爆int。(极限情况每次在1到50000插入同一个数就会有2500000000个数)
为了方便起见,我们对每个数取相反数并加上n,这样就是求k小数了。
这道题目有很多的做法,这里介绍一种树套树的方法,外层是数值线段树,内层是区间线段树。
对于外层的数值线段树,每个节点维护的是值为[l,r]的情况,而每个节点中的区间线段树维护的是这些值在[1,n]区间中的分布情况。
时间复杂度分析
插入操作:每次修改外层数值线段树上的log(n)个点,每个点中的线段树就是用传统线段树的方法(区间合并+lazy-tag标记),平摊也是log(n),所以每次插入操作时间复杂度为lognlogn。
查询操作:计算外层线段树上左孩子代表的满足条件的节点个数,也就是说计算对于左孩子所代表的线段树中满足要求区间的数的个数。如果大于c则说明在左子树,反之则在右子树。一共操作log(n)次,每次计算就是传统线段树的区间合并,平摊为log(n),所以每次查询操作时间复杂度也为lognlogn。
空间复杂度分析
有了主席树的经验,我们可以动态开节点。每次修改外层线段树上的log(n)个点,每个点新开log(n)个点,所以空间复杂度为nlognlogn,和单点修改的主席树一样,不过这题显然没有恶心地去卡空间。
具体实现
更新
void update(int rt,int l,int r)//外层数值线段树更新
{
deep(root[rt],1,n);
if (l == r) return;
int mid = (l + r) >> 1;
if (c <= mid) update(rt<<1,l,mid);
else update(rt<<1|1,mid+1,r);
}
不断在线段树上向下搜索,将符合条件的节点全部搜一遍。这里的deep函数表示在rt这个线段树中修改,写法如下:
void deep(int &rt,int l,int r)//内层区间线段树更新
{
if (rt == 0) rt = ++ tot;
if (L <= l && r <= R) {tag[rt]++; sum[rt] += r - l + 1; return;}
int mid = (l + r) >> 1;
if (L <= mid) deep(ls[rt],l,mid);
if (mid < R) deep(rs[rt],mid+1,r);
sum[rt] = sum[ls[rt]] + sum[rs[rt]] + tag[rt] * (r - l + 1);
}
这里用到了和主席树一样的写法:地址回传。然后就是和传统线段树一样的区间和并和lazy-tag标记,这里的参数简单到都不用另开一个pushdown函数下传标记。
查询
int query(int rt,int l,int r)//外层线段树查询
{
if (l == r) return l;
int mid = (l + r) >> 1;
int cnt = Sum(root[rt<<1],1,n);
if (c <= cnt) return query(rt<<1,l,mid);
c = c - cnt; return query(rt<<1|1,mid+1,r);
}
查询同样也是分为两步,Sum函数求得就是左孩子代表的线段树中满足区间要求的数的个数,写法如下:
int Sum(int rt,int l,int r)//内层线段树计数
{
if (rt == 0) return 0;
if (L <= l && r <= R) return sum[rt];
int mid = (l + r) >> 1; int cnt = 0;
if (L <= mid) cnt += Sum(ls[rt],l,mid);
if (mid < R) cnt += Sum(rs[rt],mid+1,r);
return cnt + tag[rt] * (min(R,r) - max(L,l) + 1);
}
完整代码
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1000000000
#define mod 1000000007
#define N 51000
using namespace std;
int n,m,op,L,R,c,tot;
int sum[N*400],tag[N*400],ls[N*400],rs[N*400],root[N*4];
void deep(int &rt,int l,int r)//内层区间线段树更新
{
if (rt == 0) rt = ++ tot;
if (L <= l && r <= R) {tag[rt]++; sum[rt] += r - l + 1; return;}
int mid = (l + r) >> 1;
if (L <= mid) deep(ls[rt],l,mid);
if (mid < R) deep(rs[rt],mid+1,r);
sum[rt] = sum[ls[rt]] + sum[rs[rt]] + tag[rt] * (r - l + 1);
}
void update(int rt,int l,int r)//外层数值线段树更新
{
deep(root[rt],1,n);
if (l == r) return;
int mid = (l + r) >> 1;
if (c <= mid) update(rt<<1,l,mid);
else update(rt<<1|1,mid+1,r);
}
int Sum(int rt,int l,int r)//内层线段树计数
{
if (rt == 0) return 0;
if (L <= l && r <= R) return sum[rt];
int mid = (l + r) >> 1; int cnt = 0;
if (L <= mid) cnt += Sum(ls[rt],l,mid);
if (mid < R) cnt += Sum(rs[rt],mid+1,r);
return cnt + tag[rt] * (min(R,r) - max(L,l) + 1);
}
int query(int rt,int l,int r)//外层线段树查询
{
if (l == r) return l;
int mid = (l + r) >> 1;
int cnt = Sum(root[rt<<1],1,n);
if (c <= cnt) return query(rt<<1,l,mid);
c = c - cnt; return query(rt<<1|1,mid+1,r);
}
int main()
{
scanf("%d%d",&n,&m);
while (m--)
{
scanf("%d%d%d%d",&op,&L,&R,&c);
if (op == 1) {c = n - c + 1; update(1,1,n);}
else printf("%d\n",n - query(1,1,n) + 1);
}
return 0;
}
加上离散化的代码
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define inf 1000000000
#define mod 1000000007
#define N 51000
#define lll long long
using namespace std;
int i,n,m,op[N],L[N],R[N],tot,LL,RR,t;
lll cc,b[N],c[N];
int ls[N*400],rs[N*400],root[N*4];
lll sum[N*400],tag[N*400];
void deep(int &rt,int l,int r)//内层区间线段树更新
{
if (rt == 0) rt = ++ tot;
if (LL <= l && r <= RR) {tag[rt]++; sum[rt] += r - l + 1; return;}
int mid = (l + r) >> 1;
if (LL <= mid) deep(ls[rt],l,mid);
if (mid < RR) deep(rs[rt],mid+1,r);
sum[rt] = sum[ls[rt]] + sum[rs[rt]] + tag[rt] * (r - l + 1);
}
void update(int rt,int l,int r)//外层数值线段树更新
{
deep(root[rt],1,n);
if (l == r) return;
int mid = (l + r) >> 1;
if (cc <= mid) update(rt<<1,l,mid);
else update(rt<<1|1,mid+1,r);
}
lll Sum(int rt,int l,int r)//内层线段树计数
{
if (rt == 0) return 0;
if (LL <= l && r <= RR) return sum[rt];
int mid = (l + r) >> 1; int cnt = 0;
if (LL <= mid) cnt += Sum(ls[rt],l,mid);
if (mid < RR) cnt += Sum(rs[rt],mid+1,r);
return cnt + tag[rt] * (min(RR,r) - max(LL,l) + 1);
}
int query(int rt,int l,int r)//外层线段树查询
{
if (l == r) return l;
int mid = (l + r) >> 1;
lll cnt = Sum(root[rt<<1],1,n);
if (cc <= cnt) return query(rt<<1,l,mid);
cc = cc - cnt; return query(rt<<1|1,mid+1,r);
}
int find(lll x)
{return lower_bound(b+1,b+t+1,x)-b;}
int main()
{
scanf("%d%d",&n,&m);
for (i = 1;i <= m; i++) scanf("%d%d%d%lld",&op[i],&L[i],&R[i],&c[i]);
for (i = 1;i <= m; i++) if (op[i] == 1) b[++t] = c[i];
sort(b+1,b+t+1);
t = unique(b+1,b+t+1) - (b+1);
for (i = 1;i <= m; i++)
{
LL = L[i]; RR = R[i]; cc = c[i];
if (op[i] == 1) {cc = n - find(cc) + 1; update(1,1,n);}
else printf("%d\n",b[n - query(1,1,n) + 1]);
}
return 0;
}