普通线段树
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。
线段树可以在
的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。(注意:查询时一定要注意 查询的范围不能超过所建立线段树区间的范围
)
线段树维护的信息,需要满足区间可加性,即能以可以接受的速度合并信息和修改信息,包括在使用懒惰标记时,标记也要满足可加性(例如取模就不满足可加性,对
取模然后对
取模,两个操作就不能合并在一起做)。看下图理解下(图片来自网络
模板
节点 数据向上跟新
将子节点的值更新到父节点。
//对于区间求和
inline void push_up(int rt){
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
/* 对于区间求最大值 */
void push_up(int rt) {
tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);
}
节点懒惰标记下推
对于区间求和, 原子数组值需要加上lazy标记乘以子树所统计的区间长度。 len为父节点统计的区间长度, 则len - (len >> 1)为左子树区间长度, len >> 1为右子树区间长度。
懒标记的含义是:该节点曾经被修改,但其子节点尚未被更新
inline void push_down(int rt,int len){//len = r - l +1
tree[rt<<1] += lzy[rt]*(len - (len>>1));
lzy[rt<<1] += lzy[rt];
tree[rt<<1|1] += lzy[rt]*(len>>1);
lzy[rt<<1|1] += lzy[rt];
lzy[rt] = 0;
}
对于区间求最大值, 子树的值不需要乘以长度, 所以不需要传递参数len。
void push_down(int rt) {
tree[rt << 1] += lazy[rt];
lazy[rt << 1] += lazy[rt];
tree[rt << 1 | 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
lazy[rt] = 0;
}
建树
新建一棵长度N的线段树。
void built(int rt,int l,int r){
if(l == r){
tree[rt] = read();
return ;
}
int mid = l + r >> 1;
built(rt<<1,l,mid);
built(rt<<1|1,mid+1,r);
push_up(rt);
}
更新
单点更新, 不需要用到lazy标记
void update(int p,int dat,int rt,int l,int r){
if(l == r) {
tree[rt] += dat;
return ;
}
int mid = l + r >> 1;
if(p <= m) update(p,dat,rt<<1,l,mid);
else update(p,dat,rt<<1|1,mid+1,r);
push_up(rt);
}
成段更新, 需要用到lazy标记来提高时间效率
void update(int L,int R,int dat,int rt,int l,int r){
if(L<=l&&r<=R){
tree[rt] += 1LL*(r - l + 1)*dat;
lzy[rt] += dat;
return ;
}
if(lzy[rt]) push_down(rt,r - l + 1);
int mid = l + r >> 1;
if(L <= mid) update(L,R,dat,rt<<1,l,mid);
if(R > mid) update(L,R,dat,rt<<1|1,mid+1,r);
push_up(rt);
}
查询
单点查询
int query(int p,int rt,int l,int r){
if(l == r) return tree[rt];
int mid = l + r >> 1;
if(p <= mid) return query(p,rt<<1,l,mid);
else return query(p,rt<<1|1,mid+1,r);
}
区间查询
ll query(int L,int R,int rt,int l,int r){
if(L <= l&& r<= R){
return tree[rt];
}
if(lzy[rt]) push_down(rt,r - l + 1);
int mid = l + r >> 1;
ll ans = 0;
if(L <= mid) ans += query(L,R,rt<<1,l,mid);
if(R > mid) ans += query(L,R,rt<<1|1,mid+1,r);
return ans;
}
一些例题
hdu1166 敌兵布阵
单点修改,区间查询
const int M = 50000+5;
int tree[M<<2];
void built(int rt,int l,int r){
if(l == r){
tree[rt] = read();return ;
}
int mid = l + r >> 1;
built(rt<<1,l,mid);
built(rt<<1|1,mid+1,r);
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
void update(int p,int data,int rt,int l,int r){
if(l == r) {
tree[rt] += data;//注意:是增加或者减少,不是替换!
return ;
}
int mid = l + r>>1;
if(p<=mid) update(p,data,rt<<1,l,mid);
else update(p,data,rt<<1|1,mid+1,r);
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
int query(int L,int R,int rt,int l,int r){
if(L<=l&&r<=R) return tree[rt];
int mid = l + r >> 1;
int ans = 0;
if(L<=mid) ans += query(L,R,rt<<1,l,mid);
if(R>mid) ans += query(L,R,rt<<1|1,mid+1,r);
return ans;
}
int main(){
int t = read();
rep(p,1,t){
int n = read();
built(1,1,n);
printf("Case %d:\n",p);
char op[7];
while(scanf("%s",op)!=EOF&&op[0]!='E'){
if(op[0] == 'Q'){
int l = read(),r = read();
printf("%d\n",query(l,r,1,1,n));
}
else {
int x = read(),y = read();
if(op[0] == 'A') update(x,y,1,1,n);
else update(x,-y,1,1,n);
}
}
}
}
Acwing243. 一个简单的整数问题2
区间修改,区间查询
ll tree[N<<2],lzy[N<<2];
inline void push_up(int rt){
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
inline void push_down(int rt,int len){
tree[rt<<1] += lzy[rt]*(len - (len>>1));
lzy[rt<<1] += lzy[rt];
tree[rt<<1|1] += lzy[rt]*(len>>1);
lzy[rt<<1|1] += lzy[rt];
lzy[rt] = 0;
}
void built(int rt,int l,int r){
if(l == r){
tree[rt] = read();
return ;
}
int mid = l + r >> 1;
built(rt<<1,l,mid);
built(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int L,int R,int dat,int rt,int l,int r){
if(L<=l&&r<=R){
tree[rt] += 1LL*(r - l + 1)*dat;
lzy[rt] += dat;
return ;
}
if(lzy[rt]) push_down(rt,r - l + 1);
int mid = l + r >> 1;
if(L <= mid) update(L,R,dat,rt<<1,l,mid);
if(R > mid) update(L,R,dat,rt<<1|1,mid+1,r);
push_up(rt);
}
ll query(int L,int R,int rt,int l,int r){
if(L <= l&& r<= R){
return tree[rt];
}
if(lzy[rt]) push_down(rt,r - l + 1);
int mid = l + r >> 1;
ll ans = 0;
if(L <= mid) ans += query(L,R,rt<<1,l,mid);
if(R > mid) ans += query(L,R,rt<<1|1,mid+1,r);
return ans;
}
int main(){
int n = read(),m = read();
built(1,1,n);
while(m--){
char q = gc();
if(q == 'Q'){
int l = read(),r = read();
if(l > r) swap(l,r);
printf("%lld\n",query(l,r,1,1,n));
}
else {
int l = read(),r = read(),d= read();
update(l,r,d,1,1,n);
}
}
}
P3373 【模板】线段树 2
区间修改,区间查询
思路:
tmd,*******************************,sb取模卡我。。。。
这个题目维护两个 标记,加法标记和乘法标记。
然后区间更新时,记住乘法优先的原则。
sum(l,r) * x1 + x2 乘以个 k 等价于 sum(l,r)* x1 * k + x2*k
sum(l,r) * x1 + x2 加上一个 k 等价于sum(l,r) * x1 + (x2 + k)
ll tree[N<<2],add[N<<2],mul[N<<2];
inline void push_up(int rt){
tree[rt] = (tree[rt<<1] + tree[rt<<1|1]) % M;
}
inline void push_down(int rt,int len){
tree[rt<<1] = (tree[rt<<1]*mul[rt]%M + 1LL*(len-(len>>1))*add[rt]%M)%M;
tree[rt<<1|1] = (tree[rt<<1|1]*mul[rt]%M + 1LL*(len>>1)*add[rt]%M)%M;
mul[rt<<1] = (mul[rt<<1]*mul[rt])%M;
mul[rt<<1|1] = (mul[rt<<1|1]*mul[rt])%M;
add[rt<<1] = (add[rt<<1]*mul[rt]%M + add[rt])%M;
add[rt<<1|1] = (add[rt<<1|1]*mul[rt]%M+ add[rt])%M;
add[rt] = 0;mul[rt] = 1;
}
void built(int rt,int l,int r){
add[rt] = 0;mul[rt] = 1;
if(l == r) {tree[rt] = read()%M;return ;}
int mid = l + r >> 1;
built(rt<<1,l,mid);
built(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int L,int R,ll k,int flag,int rt,int l,int r){
if(L <= l && r <= R){
if(flag ==1 ){//乘法运算
tree[rt] = (tree[rt]*k)%M;
mul[rt] = (mul[rt]*k)%M;
add[rt] = (add[rt]*k)%M;
}
else {//加法运算
tree[rt] = (tree[rt] + 1LL*(r-l+1)*k%M)%M;
add[rt] = (add[rt] + k)%M;
}
return ;
}
if(add[rt]||mul[rt]!=1) push_down(rt,r-l+1);
int mid = l + r >> 1;
if(L <= mid) update(L,R,k,flag,rt<<1,l,mid);
if(R > mid) update(L,R,k,flag,rt<<1|1,mid+1,r);
push_up(rt);
}
ll query(int L,int R,int rt,int l,int r){
if(L <= l&&r <= R) return tree[rt];
if(add[rt]||mul[rt]!=1) push_down(rt,r-l+1);
int mid = l + r >> 1;
ll ans = 0;
if(L <= mid) ans = (ans + query(L,R,rt<<1,l,mid))%M;
if(R > mid) ans = (ans + query(L,R,rt<<1|1,mid+1,r)) % M;
return ans;
}
int main(){
int n = read(),m = read(),p = read();
M = p;
built(1,1,n);
while(m--){
int op = read(),l =read(),r = read();
if(op == 1){
ll k = read();
update(l,r,k,1,1,1,n);
}
else if(op == 2){
ll k = read();
update(l,r,k,2,1,1,n);
}
else cout<<query(l,r,1,1,n)<<endl;
}
}
GSS5 - Can you answer these queries V
思路:
这个题目和上一个题目的区别在于,这个题目给定区间端点是在一个范围内,然后我们可以进行分类讨论。
1、这两个区间是相离的,那么这个最后查询的区间,肯定会包括 ,所以我们要求的答案为
2、这两个区间是相交的( ),
1)如果这两个区间重合,那么答案是
2)不重合的情况,答案为 ,因为我们多算了一次 ,所以要减去
3、相交的情况,有点复杂
1)如果两个端点在相交的区域,那么答案为
2)如果一个在相交的区域,一个不在,那么答案是 ,注意还要减去多算的元素,这个情况有两个小情况,在左在右的问题。
3)如果两个端点都不在相交的区域,那么答案类似于第一种情况,
最后,取个 就是答案了。
struct Segment{
int lmax,rmax,sum,ms;
Segment(){
lmax = rmax = sum = ms = 0;
}
}tree[N<<2];int q[N];
void built(int rt,int l,int r){
if(l == r){
q[l] = tree[rt].lmax = tree[rt].rmax = tree[rt].sum = tree[rt].ms = read();
return ;
}
int mid = l + r >> 1;
built(rt<<1,l,mid);
built(rt<<1|1,mid +1,r);
tree[rt].sum = tree[rt<<1].sum + tree[rt<<1|1].sum;
tree[rt].lmax = max(tree[rt<<1].lmax,tree[rt<<1].sum + tree[rt<<1|1].lmax);
tree[rt].rmax = max(tree[rt<<1|1].rmax,tree[rt<<1|1].sum + tree[rt<<1].rmax);
tree[rt].ms = max(tree[rt<<1].ms,tree[rt<<1|1].ms);
tree[rt].ms = max(tree[rt].ms,tree[rt<<1].rmax+tree[rt<<1|1].lmax);
}
Segment query(int L,int R,int rt,int l,int r){
if(L > R) return Segment();//区间不合理情况,直接返回
if(L<=l&&r<=R){
return tree[rt];
}
int mid = l +r >>1;
Segment a,b,ans;
if(R<=mid) return query(L,R,rt<<1,l,mid);
else if(L>mid) return query(L,R,rt<<1|1,mid+1,r);
else {
a = query(L,R,rt<<1,l,mid);
b = query(L,R,rt<<1|1,mid+1,r);
ans.sum = a.sum + b.sum;
ans.lmax = max(a.lmax,a.sum + b.lmax);
ans.rmax = max(b.rmax,b.sum + a.rmax);
ans.ms = max(a.ms ,b.ms);
ans.ms = max(ans.ms,a.rmax + b.lmax);
return ans;
}
}
int main(){
int t = read();
while(t--){
int n = read();
built(1,1,n);
int m = read();
while(m--){
int l1 = read(),r1 = read(),l2 = read(),r2 = read();
if(r1 < l2){
cout<<query(r1+1,l2-1,1,1,n).sum + query(l1,r1,1,1,n).rmax + query(l2,r2,1,1,n).lmax<<endl;
}
else if(r1 == l2){
if(l1 == r2) cout<<query(l1,r1,1,1,n).ms<<endl;
else {
cout<<query(l1,r1,1,1,n).rmax + query(l2,r2,1,1,n).lmax - q[r1]<<endl;
}
}
else {
int a = query(l2,r1,1,1,n).ms;
int b = query(l1,l2,1,1,n).rmax+query(l2,r1,1,1,n).lmax - q[l2];
int c = query(l2,r1,1,1,n).rmax + query(r1,r2,1,1,n).lmax - q[r1];
int d = query(l2+1,r1-1,1,1,n).sum + query(l1,l2,1,1,n).rmax + query(r1,r2,1,1,n).lmax;
cout << max(max(a,d),max(b,c))<<endl;
}
}
}
}
懒标记的运用
hihoCoder#1078 : 线段树的区间修改
题意:
给你一个序列,有
个数字,然后,你要进行区间修改,区间和查询,区间修改是将
的数全变为
。
思路:
一开始想的是只需要将包含的区间的区加和修改一下就行,写出来发现不对,如果这个区间在查询时被破坏,下面的数字没有发生改变,所以引进懒标记,用来告诉它的儿子们,他们是谁(心里没点阿拉伯数字
ll tree[N<<2],lzy[N<<2];
inline void push_up(int rt){
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
inline void push_down(int rt,int len){
tree[rt<<1] = lzy[rt]*(len-(len>>1));
tree[rt<<1|1] = lzy[rt] * (len>>1);
lzy[rt<<1] = lzy[rt];lzy[rt<<1|1] = lzy[rt];
lzy[rt] = 0;
}
void built(int rt,int l,int r){
if(l == r) {
tree[rt] = read();
return ;
}
int mid = l + r >> 1;
built(rt<<1,l,mid);
built(rt<<1|1,mid+1,r);
push_up(rt);
}
void update(int L,int R,int dat,int rt,int l,int r){
if(L<=l&&r <= R){
tree[rt] = (r-l+1)*dat;
lzy[rt] = dat;
return ;
}
if(lzy[rt]) push_down(rt,r - l + 1);
int mid = l + r >> 1;
if(L<=mid) update(L,R,dat,rt<<1,l,mid);
if(R>mid) update(L,R,dat,rt<<1|1,mid+1,r);
push_up(rt);
}
int query(int L,int R,int rt,int l,int r){
if(L<=l&&r<=R){
return tree[rt];
}
if(lzy[rt]) push_down(rt,r-l+1);
int mid = l + r >> 1;
int ans=0;
if(L<=mid) ans += query(L,R,rt<<1,l,mid);
if(R>mid) ans += query(L,R,rt<<1|1,mid+1,r);
return ans;
}
int main(){
int n = read();
built(1,1,n);
int m = read();
while(m--){
int op = read(),l = read(),r = read();
if(op == 1){
int k = read();
update(l,r,k,1,1,n);
}
else cout<<query(l,r,1,1,n)<<endl;
}
}
权值线段树
权值线段树和普通线段树的区别是,普通线段树是维护给定的区间,而权值线段树是维护值域中数的出现次数。
借用网上的图片(侵删
那么我们可以用它来做什么呢,可以求这个序列的第
大/小问题。因为至于的范围可能很大,所以往往我们还伴随着离散化。
我们找找这个序列的第9小(共有12个数,也可以说第4大)
我们从根节点往下找,左子树值为6 <9,所以我们往右子树去,同时,减去左子树的权值
,变为了3,然后在看其左子树权值,为4>3,所以往左子树去,然后再比较权值,直到 到叶子节点,就是答案了,这里答案就是6。
实现的话还是比较好写的,举个栗子。
P1138 第k小整数
思路:
用权值线段树可以在
的时间内就可以找到了。
int a[N],b[N];
int tree[N<<2];
void update(int pos,int rt,int l,int r){
if(l == r){
//tree[rt] ++;
tree[rt] = 1;
return ;
}
int mid = l + r >> 1;
if(pos <=mid) update(pos,rt<<1,l,mid);
else update(pos,rt<<1|1,mid+1,r);
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
int kth(int k,int rt,int l,int r){
if(l == r){
return b[l];
}
int mid = l + r >> 1;
if(k<=tree[rt<<1]) return kth(k,rt<<1,l,mid);
else return kth(k-tree[rt<<1],rt<<1|1,mid+1,r);
}
int main(){
int n = read(),k = read();
for(int i = 1;i <= n;++i) a[i] = b[i] = read();
sort(b+1,b+n+1);
int m = unique(b+1,b+n+1) - b - 1;
rep(i,1,n) a[i] = lower_bound(b+1,b+m+1,a[i]) - b;
for(int i = 1;i <= n;++i) update(a[i],1,1,n);
if(tree[1] < k) puts("NO RESULT");
else cout << kth(k,1,1,n);
}
可持久化线段树(主席树)
持久化数据结构思想,就是保留整个操作的历史,即,对一个线段树进行操作之后,保留访问操作前的线段树的能力。
主席树有一个很简单的应用,查询指定区间第
大/小。
我们知道权值线段树可以很简单的求出整个序列的第
大/小。我们原来的建立权值线段树的过程,是不断插入值,那么,我们在插入的时候保留原来的权值线段树,在这一个基础上再插入新建一棵权值线段树,这样就建立了
棵权值线段树。
接下来有个性质,就是权值线段树具有区间可减性
,第
棵权值线段树上的权值减去第
棵权值线段树上的权值,就是
这个区间所对应的权值线段树。
但是建立
棵权值线段树,时空肯定会爆炸。那么,我们能不能在上一棵权值线段树的基础上建立呢,看下图(来自网络,侵删
比如上述图片插入4,我们发现,只会影响一条路径,而这个路径的长度是 的,那么我们就可以整体以 的空间复杂度建树了。
关于可持久化线段树的理解,感觉知乎上一位大佬说的很好,分享一下
一道模板题
直接上模板吧,模板来自oi-wiki。另外,建立主席树时,不能用一般的堆式建树,而需要动态开点,具体说就是需要保存左右儿子的下标,用结构体或者数组,应该是要不停建树吧,而不单单是一棵线段树了。还有关于数组的大小,oi-wike上说开
,也就是32倍,也有的人喜欢开40倍,感觉32倍就足够了吧。
int a[N],b[N];
int rt[N<<5],ls[N<<5],rs[N<<5],tree[N<<5];
int tot;
int built(int l,int r){
int root = ++ tot;
if(l == r) return root;
int mid = l + r >>1;
ls[root] = built(l,mid);
rs[root] = built(mid+1,r);
return root ;
}
int update(int dat,int root,int l,int r){
int dir = ++ tot;
ls[dir] = ls[root],rs[dir] = rs[root],tree[dir] = tree[root] + 1;
if(l == r) return dir;
int mid = l + r >> 1;
if(dat <= mid) ls[dir] = update(dat,ls[dir],l,mid);
else rs[dir] = update(dat,rs[dir],mid+1,r);
return dir;
}
int query(int root,int rot,int k,int l,int r){
int x = tree[ls[root]] - tree[ls[rot]];
if(l == r) return l;
int mid = l + r >>1;
if(k<=x) return query(ls[root],ls[rot],k,l,mid);
else return query(rs[root],rs[rot],k-x,mid+1,r);
}
int main(){
int n = read(),m = read();
rep(i,1,n) a[i] = b[i] = read();
sort(b+1,b+n+1);
int q = unique(b+1,b+n+1) - b - 1;
rep(i,1,n) a[i] = lower_bound(b+1,b+q+1,a[i]) - b;
rt[0] = built(1,n);
rep(i,1,n) rt[i] = update(a[i],rt[i-1],1,n);
rep(i,1,m){
int l = read(),r = read(),k = read();
printf("%d\n",b[query(rt[r],rt[l-1],k,1,n)]);
}
}
bzoj 2588: Spoj 10628. Count on a tree
思路:
算是主席树模板题吧,只不过是在树上建立,还需要
。
这个题目强制在线处理。询问你两个节点之间路径上的第
小权值。这里不妨假设根为
,然后从根
过程中建立权值线段树。这样就处理没个节点到根路径上的权值线段树了,也是前缀和,然后若询问
两个节点的第
小,和序列上的第
小类似,我们也要做差,来求出
路径上的权值线段树。
这样就求出
路径上的点出现的次数的权值线段树了。
吐槽一下:woc,* * *,f * *k,因为
和
函数搞混,tm debug了很久,然后到处看别人的博客,感觉没大问题啊,结果一直RE ,RE,RE…我裂开了。
不过倒也发现了一个问题,网上的博客中,虽然在
AC了,但是在spoj上依旧RE,甚至连spoj的样例都过不了,当然,也包括我的,在这个题目上花费了太多时间了,先跳过,以后再说。最后贴上能在
能过的神仙代码。
int a[N],b[N];
int rt[N*40],tree[N*40],ls[N*40],rs[N*40];
int tot,n,m,q;
struct Edge
{
int next;
int to;
}edge[N<<1];
int head[N],cnt;
inline void add(int from,int to){
edge[++cnt].next = head[from];
edge[cnt].to = to;
head[from] = cnt;
}
int depth[N+5],fa[N+5][30],lg[N+5];
int built(int l,int r){
int root = ++ tot;
if(l == r){
return root;
}
int mid = l + r >> 1;
ls[root] = built(l,mid);
rs[root] = built(mid+1,r);
return root;
}
int update(int dat,int root,int l,int r){
int dir = ++ tot;
ls[dir] = ls[root],rs[dir] = rs[root];tree[dir] = tree[root] + 1;
if(l == r) return dir;
int mid = l + r >> 1;
if(dat <= mid) ls[dir] = update(dat,ls[dir],l,mid);
else rs[dir] = update(dat,rs[dir],mid+1,r);
return dir;
}
int FA[N];
void dfs(int x,int Fa){
for(int i = head[x];i;i= edge[i].next){
int y = edge[i].to;
if(y == Fa) continue;
FA[y] = x;
rt[y] = update(a[y],rt[x],1,q);
dfs(y,x);
}
}
void DFS(int f,int fath)
{
depth[f]=depth[fath]+1;
fa[f][0]=fath;
for(int i=1;(1<<i)<=depth[f];i++)
fa[f][i]=fa[fa[f][i-1]][i-1];
for(int i=head[f];i;i=edge[i].next)
if(edge[i].to!=fath)
DFS(edge[i].to,f);
}
int lca(int x,int y)
{
if(depth[x]<depth[y])
swap(x,y);
while(depth[x]>depth[y])
x=fa[x][lg[depth[x]-depth[y]]-1];
if(x==y)
return x;
for(int k=lg[depth[x]]-1;k>=0;k--)
if(fa[x][k]!=fa[y][k])
x=fa[x][k], y=fa[y][k];
return fa[x][0];
}
int query(int u,int v,int la,int fath,int k,int l,int r){
int x = tree[ls[u]] + tree[ls[v]] - tree[ls[la]] - tree[ls[fath]];
if(l == r) return l;
int mid = l + r >> 1;
if(k <= x) return query(ls[u],ls[v],ls[la],ls[fath],k,l,mid);
else return query(rs[u],rs[v],rs[la],rs[fath],k-x,mid+1,r);
}
int main(){
n = read(),m = read();
rep(i,1,n) a[i] = b[i] = read();
//离散化
sort(b+1,b+n+1);
q = unique(b+1,b+n+1) - b - 1;
rep(i,1,n) a[i] = lower_bound(b+1,b+q+1,a[i]) - b;
rep(i,1,n-1){
int u = read(),v = read();
add(u,v);
add(v,u);
}
rt[0] = built(1,q);
rt[1] = update(a[1],rt[0],1,q);
dfs(1,0);//建立主席树
//lca
DFS(1,0);//1
for(int i = 1;i<=n;++i){//2
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}
ll las = 0;
rep(i,1,m){
int u = read(),v = read(),k = read();
u ^= las;
int la = lca(u,v);
las = b[query(rt[u],rt[v],rt[la],rt[FA[la]],k,1,q)];
printf("%lld",las);
if(i!=m) puts("");
}
return 0;
}
神仙代码在此
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define fd(i,a,b) for (int (i)=(a);(i)>=(b);(i)--)
using namespace std;
const int M=100000+5;
int g[M][21],Next[M*2],head[M],to[M*2],rt[4001000],s[4001000],ls[4001000],rs[ 4001000];
int h[M],val[M],v,d[M],cd,tot,cnt;
void R(int &n)
{
int t=0,p=1;char ch;
for(ch=getchar ();!('0'<=ch && ch<='9');ch=getchar())
if(ch=='-') p=-1;
for(;'0'<=ch && ch<='9';ch=getchar()) t=t*10+ch-'0';
n=t*p;
}
void add(int x,int y)
{
to[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
}
void ch(int x,int &y,int l,int r,int v)
{
s[y=++cnt]=s[x]+1;
if (l==r) return ;
int m=l+r>>1;
if (v<=m)
{
rs[y]=rs[x];
ch(ls[x],ls[y],l,m,v);
}
else
{
ls[y]=ls[x];
ch(rs[x],rs[y],m+1,r,v);
}
}
int query(int x,int y,int z,int w,int l,int r,int k)
{
if (l==r) return l;
int tmp=s[ls[y]]+s[ls[z]]-s[ls[x]]-s[ls[w]];
int m=l+r>>1;
if (tmp>=k) return query(ls[x],ls[y],ls[z],ls[w],l,m,k);
else return query(rs[x],rs[y],rs[z],rs[w],m+1,r,k-tmp);
}
void dfs(int x,int y)
{
for (int i=head[x];i;i=Next[i])
{
v=to[i];
if (y!=v)
{
d[v]=d[x]+1;
g[v][0]=x;
ch(rt[x],rt[v],1,cd,val[v]);
dfs(v,x);
}
}
}
int lca(int x,int y)
{
if (d[x]<d[y]) swap(x,y);
fd(k,20,0)
if (d[g[x][k]]>d[y]) x=g[x][k];
if (d[x]!=d[y]) x=g[x][0];
fd(k,20,0)
if (g[x][k]!=g[y][k])
x=g[x][k],y=g[y][k];
if (x!=y) return g[x][0];
else return x;
}
int main()
{
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int n,m,x,y,z,k;
R(n);R(m);
fo(i,1,n)R(val[i]),h[i]=val[i];
sort(h+1,h+1+n);
cd=unique(h+1,h+1+n)-(h+1);
fo(i,1,n)val[i]=lower_bound(h+1,h+1+cd,val[i])-h;
fo(i,1,n-1)
{
R(x);R(y);
add(x,y);add(y,x);
}
ch(rt[0],rt[1],1,cd,val[1]);
d[1]=1;
dfs(1,0);
fo(j,1,20)
fo(i,1,n)
g[i][j]=g[g[i][j-1]][j-1];
g[1][0]=0;
int ans=0;
while (m--)
{
R(x);R(y);R(k);
z=lca(x,y);
ans=h[query(rt[z],rt[x],rt[y],rt[g[z][0]],1,cd,k)];
printf("%d\n",ans);
}
return 0;
}
待补:zkw线段树,李超线段树,扫描线