普通線段樹
線段樹是算法競賽中常用的用來維護 區間信息 的數據結構。
線段樹可以在 的時間複雜度內實現單點修改、區間修改、區間查詢(區間求和,求區間最大值,求區間最小值)等操作。(注意:查詢時一定要注意 查詢的範圍不能超過所建立線段樹區間的範圍
)
線段樹維護的信息,需要滿足區間可加性,即能以可以接受的速度合併信息和修改信息,包括在使用懶惰標記時,標記也要滿足可加性(例如取模就不滿足可加性,對 取模然後對 取模,兩個操作就不能合併在一起做)。看下圖理解下(圖片來自網絡
模板
節點 數據向上跟新
將子節點的值更新到父節點。
//對於區間求和
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線段樹,李超線段樹,掃描線