真 · 弱鸡解读,大佬退散 /// >。< ///
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
特别感谢zzy小公举对着我满是bug的程序改了一晚上,辛苦啦~
题意
给定一个数列,有若干询问,询问 l~r 区间内,距离p第k近的元素与p的距离。每次询问内容都要与上次询问的答案按位异或,初始答案为0。
思路
首先和区间第k大有关,肯定是要用主席树啦~
其次因为问题问的不是区间第k大,而是第k小距离,但距离这个东西是相对于询问中给的p而言的,我们显然不可能每次根据p去修改主席树。
那么有没有什么方法,可以让这个距离反映在原数列上,且与p无关呢?
可以想,距离第k小,假设这个距离为 x,其实也就是相当于在询问的区间里,
[
p
−
x
,
p
+
x
]
[p-x,p+x]
[p−x,p+x]范围内的数有k个。(闭区间哦)。
这样的话,我们可以不断地猜测这个x,然后通过查询区间里
[
p
−
x
,
p
+
x
]
[p-x,p+x]
[p−x,p+x]范围内的数的个数来验证我们的答案,也就是二分答案啦。
需要注意的许多细节
都是我错过的
这次的树根集合
T
[
i
]
T[i]
T[i]表示的是权值!(感觉是会好理解一丢丢。。)
T
[
i
]
T[i]
T[i]表示第0~i小的数,所以求的时候看起来大概就是
T
[
p
+
x
]
−
T
[
p
−
x
−
1
]
T[p+x]-T[p-x-1]
T[p+x]−T[p−x−1]类似的亚子。
- 许多主席树的板子在离散化的时候都用了sort+unique函数,但是不知道为什么,这题不可以。(也许是我用得不对,求知情dalao指正)。
这里我用的是直接以 a [ i ] a[i] a[i]的值对 i d [ i ] id[i] id[i](数字在原数列中的位置)排序,在建树的时候再依照原先的下标,从1到n,插入 i d [ i ] id[i] id[i]。可以认为 i d [ i ] id[i] id[i]是离散的之后 a [ i ] a[i] a[i],因为题目保证了 a [ i ] a[i] a[i]互不相同,在排序之后,原来有序的 id 就会从 ai 的位置代号变成 ai 的大小编号(自己在纸上试试就知道啦),所以就代替了原来的一顿操作啦。 - 因为是距离嘛,虽然保证了
a
[
i
]
a[i]
a[i]互不相同,但是距离相同的数还是会有的。所以要处理好边界。
以我的程序为例。
while(L <= R){
mid = (L+R)>>1;
lb = lower_bound(t+1, t+n+1, p-mid)-t;
ub = upper_bound(t+1, t+n+1, p+mid)-t-1;
ans = 0;
query(1,n,T[lb-1],T[ub],l,r);
if(ans >= k) {
x = mid;
R = mid-1;
}
else L = mid+1;
}
求出ans之后,它可能大于等于k,也可能小于k,如果小于k是肯定不行了,但是如果大于等于k。。
打个比方,比如有一群数,他们的距离是1 1 2 2 3,我们 要寻找距离第3大。
我们二分答案,假设开始时 l=0,r=3,mid=1,假设ans表示距离小于等于mid的数的个数。这时候求出来 ans=2,小于我们要找的3,所以 l 变成 mid+1。
现在 l=2,r=3,mid=2,这次求出来 ans=4,大于k了,但是我们肉眼观察就可以知道,mid=2 是正确的半径。于是我们应该在 ans>=k 的时候先把半径记录下来,然后继续循环。 r 还是要等于 mid-1 啦。
现在 l=2,r=1,不满足循环条件了,于是退出循环~(这例子不够长,不过大概就是这么个意思。。)
- 查询函数query里边的变量的含义一定要明确,不要把自己转晕。
比如我的query是这个样子的。
void query(int l,int r,int lr,int rr,int L,int R)
其中,l 和 r 是询问的总区间,初始永远是 1~ n,经过递归变成 L ~ R。萌新(说的就是我自己 )注意哦,在这一题里,n在这里代表的是数列的总长度 n ,不是多少种权值的那个n。
L、R是询问的区间,嗯,就是q组询问那个询问,把它定义成全局变量就不用每次都传进去了。
lr 和 rr ,也就是 left_root 和 right_root。因为主席树要用两棵线段树做差,求出权值位于其中间的树的个数,所以我们需要这两棵树的树根。
那么,树根到底怎么找到呢?
我们现在已知的是一个区间
[
p
−
x
,
p
+
x
]
[p-x,p+x]
[p−x,p+x],我们可以求出区间端点对应的“第几大”,然后就可以啦。
感觉自己说的全是废话
代码全文
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
const int M = MAXN * 30;
int n,q,m,tot,ans=0,k;
int t[MAXN];
int T[M], lson[M], rson[M], c[M],id[MAXN];
int build(int l,int r){
int root=tot++;
c[root]=0;
if(l!=r){
int mid=(l+r)>>1;
lson[root]=build(l,mid);
rson[root]=build(mid+1,r);
}
return root;
}
int update(int root,int pos,int val){
int newroot=tot++,tmp=newroot;
c[newroot]=c[root]+val;
int l=1,r=n;
while(l<r){
int mid=(l+r)>>1;
if(pos<=mid){
lson[newroot]=tot++;rson[newroot]=rson[root];
newroot = lson[newroot];root=lson[root];
r=mid;
}
else {
rson[newroot]=tot++;lson[newroot]=lson[root];
newroot=rson[newroot];root=rson[root];
l=mid+1;
}
c[newroot]=c[root]+val;
}
return tmp;
}
//返回lk~rk的数的个数 不包含端点
void query(int l,int r,int lr,int rr,int L,int R){
if(ans >= k) return;
if(L <= l &&r <= R){
ans += c[rr]-c[lr];
return;
}
int mid = (l+r)>>1;
if(L <= mid) query(l,mid,lson[lr],lson[rr],L,R);
if(mid < R) query(mid+1,r,rson[lr],rson[rr],L,R);
return ;
}
bool cmp(int x,int y){
return t[x]<t[y];
}
int main(){
int Tc;
scanf("%d",&Tc);
while(Tc --){
scanf("%d%d",&n,&q);
tot = 0;
for(int i = 1;i <= n;i++)
scanf("%d",&t[i]),id[i]=i;
sort(id+1,id+1+n,cmp);
sort(t+1,t+n+1);
T[0]= build(1,n);
for(int i=1;i<=n;i++){
T[i]=update(T[i-1],id[i],1);
}
int l,r,p,x=0,tmp,L,R,mid,lb,ub;
while(q--){
scanf("%d%d%d%d",&l,&r,&p,&k);
l ^= x;r ^= x;p ^= x;k ^= x;
L=0, R=1e6;
while(L <= R){
mid = (L+R)>>1;
lb = lower_bound(t+1, t+n+1, p-mid)-t;
ub = upper_bound(t+1, t+n+1, p+mid)-t-1;
ans = 0;
query(1,n,T[lb-1],T[ub],l,r);
if(ans >= k) {
x = mid;
R = mid-1;
}
else L = mid+1;
}
printf("%d\n",x);
}
}
return 0;
}
后记
主席树萌新写这个题真的是瑟瑟发抖啊,算起来也是奋斗了一天半呢,眼睛都被辣哭了,心态也是各种崩。
再次感谢zzy小公举啦!还要de玄学bug,还要忍受我的暴脾气。
不过感觉收获也是很多的。
首先是写什么题目,一定要想好再下手啊!对于每个变量是干嘛的,一定要写清楚!不要乱起名字,也不要想当然。
其次,玄学的、不能驾驭的东西,还是不要乱用啦。能驾驭的话省时省力固然好,但是如果不是很清楚的话,还是宁愿多费点功夫,也不要去碰运气啦。
还是要明白,自己在写什么吧。
三水小姐姐好厉害嘤嘤嘤!!!!!!