AtCoder Beginner Contest 223 題解
阿新 • • 發佈:2021-10-19
線段樹是一種用於區間更新,單點更新,區間查詢的一種演算法。
比如:區間染色,區間求和(也可用樹狀陣列),區間最值(也可用RMQ演算法,但RMQ為數值不可更新)
主要思路是,t[1]為根節點,t[k<<1],t[k<<1|1] 為 t[k] 的左右節點 t[k]的值為節點的值
如果一個數組長度為N,那麼以t的長度最長為N*4。
以求最值為例
初始化
#define MAXN 10000 int t[MAXN<<2]; int va[MAXN];//原始數值 int lazy[MAXN<<2]; void pushUp(int k){ t[k] = max(t[k<<1],t[k<<1|1]); } void build(int l,int r,int k){ if ( l==r ){ t[k] = va[l]; }else{ int mid = l+((r-l)>>1); build(l,mid,k<<1); build(mid+1,r,k<<1|1); pushUp(k); } }
單點更新
void update(int p,int av,int l,int r,int k){ if ( l==r ){ t[k] += av;//此時 p==l && p==r 但是跟k不相等 va[k] += av; }else{ int mid = l+((r-l)>>1); if ( p <= mid ){ update(p,av,l,mid,k<<1); }else{ update(p,av,mid+1,r,k<<1|1); } pushUp(k);//更新了子樹,當然要更新當前節點。 } }
區間查詢
int query(int L,int R,int l,int r,int k){ if ( L<= l && r <= R ){//當小於的時候,是返回給父節點,進行求max運算的,所以也是返回t[k] 即可 return t[k]; } int ret = INT_MIN; int mid = l+((r-l)>>1); if ( L <= mid ){//需要查詢的區間被左子樹部分包含 ret = max(ret,query(L,R,l,mid,k<<1); } if ( mid < R ) {//需要查詢的區間被左子樹部分包含 ret = max(ret,query(L,R,mid+1,r,k<<1|1); } return ret; }
區間更新
假設,現在要更新所有陣列值,都+3,那麼,如果按非常樸素的想法來看,那麼時而要個改整個線段樹,這樣也太不高效了,所以引入了一個lazy
當真正需要值時,才把子樹的值變化。
lazy[k] = ? 是指,作為t[k] 已經更新過了,但是t[k<<1] t[k<<1|1] 還沒更新過
所以,當清除lazy[k]的時候,需要把t[k<<1] t[k<<1|1] 也設定一下
void pushDown(int k){
if ( lazy[k] > 0 ){
t[k<<1] += lazy[k];
t[k<<1|1] += lazy[k];
lazy[k<<1] += lazy[k];
lazy[k<<1|1] += lazy[k];
}
}
void updateRange(int L,int R,int av,int l,int r,int k){
if ( L<=l && r<=R ){
t[k] += av;
lazy[k] += av;//注意這裡是+=,也就是如果之前有lazy沒有完成,那麼也是會有效的
}else{
pushDown(k);//如果子樹還沒有被更新到,那麼是需要更新的
int mid = l+((r-l)>>1);
if ( L <= mid )
updateRange(L,R,av,l,mid,k<<1);
if ( mid < R)
updateRange(L,R,av,mid+1,r,k<<1|1);
pushUp(k);//更新了子樹,當然要更新當前節點。
}
}
//query也需要更改
int queryRange(int L,int R,int l,int r,int k){
if ( L <= l && r <= R ){
return t[k];
}
int ret = INT_MIN;
int mid = l+((r-l)>>1);
if ( L <= mid ){
ret = max(ret,queryRange(L,R,l,mid,k<<1);
}
if ( mid < R ){
ret = max(ret,queryRange(L,R,mid+1,r,k<<1|1);
}
return ret;
}
附poj2777 區間染色的ac程式碼
#include <iostream>
#include <cassert>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <cmath>
#include <climits>
#include <functional>
#include <list>
#include <cstdlib>
#include <set>
#include <stack>
#include <map>
#include <algorithm>
#define ll long long
using namespace std;
#define MAXN 100010
int t[MAXN<<2];
int lazy[MAXN<<2];
int va[MAXN];
void pushUp(int k){
t[k] = t[k<<1] | t[k<<1|1];
}
void pushDown(int k){
if ( lazy[k] > 0 ){
t[k<<1] = lazy[k];
t[k<<1|1] = lazy[k];
lazy[k<<1] = lazy[k];
lazy[k<<1|1] = lazy[k];
lazy[k] = 0;
}
}
void build(int l,int r,int k){
if ( l == r ){
t[k] = va[l];
}else{
int mid = l+((r-l) >> 1);
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
pushUp(k);
}
}
void update(int L,int R,int v,int l,int r,int k){
if ( L <= l && r <= R ){
t[k] = v;
lazy[k] = v;
}else{
pushDown(k);
int mid = l+((r-l) >> 1);
if ( L <= mid ){
update(L,R,v,l,mid,k<<1);
}
if ( mid < R ){
update(L,R,v,mid+1,r,k<<1|1);
}
pushUp(k);
}
}
int query(int L,int R,int l,int r,int k){
if ( L <= l && r <= R ){
return t[k];
}
pushDown(k);
int mid = l+((r-l) >> 1);
int ret = 0;
if ( L <= mid ){
ret |= query(L,R,l,mid,k<<1);
}
if ( mid < R ){
ret |= query(L,R,mid+1,r,k<<1|1);
}
return ret;
}
int getCnt(int x){
int sum = 0;
while( x > 0 ){
x = x&(x-1);
++sum ;
}
return sum;
}
int main(){
int L,T,O;
scanf("%d%d%d",&L,&T,&O);
for( int i=0;i<=L+9;++i ){
va[i] = 1;
}
build(1,L,1);
char c;
int A,B,C;
for( int i=0;i<O;++i ){
scanf("%c",&c);
scanf("%c",&c);
if ( c == 'C' ){
scanf(" %d %d %d",&A,&B,&C);
int a = min(A,B);
int b = max(A,B);
update(a,b,1<<(C-1),1,L,1);
}else{
scanf(" %d %d",&A,&B);
int a = min(A,B);
int b = max(A,B);
int ret = query(a,b,1,L,1);
printf("%d\n",getCnt(ret));
}
}
return 0;
}