数据结构总结——线段树
此博客仅为总结,不适合新手
线段树
线段树(segment tree)是一种维护区间信息的数据结构, 如下图

其特点在于运用了二分的思想,将一段长度为n的区间分开为logn层,然后我们每次查询区间时最多会访问logn个区间
下面代码风格可能由于历史悠久导致不同,所以最好不要学习我的代码,完整的线段树题目可以看我的csdn博客,当时我搞这个线段树还是很恼火的
初级应用——序列操作
例一
给你N个数,有两种操作
1:给区间[a,b]的所有数都增加X
2:询问第i个数是什么?
怀念丁神idy002教我们的指针版线段树
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const int N = 100000+10; struct Node { ll flag,sum; Node *ls, *rs; }pool[N*2+5], *tail = pool, *root; int n,q; int aa[N]; Node *build( int lf, int rg ) { Node *nd = ++tail; if( rg == lf ) { nd->sum = aa[lf]; nd->flag = 0; } else { int mid = (lf + rg) >> 1; nd->ls = build( lf, mid ); nd->rs = build( mid+1, rg ); nd->sum = nd->ls->sum + nd->rs->sum; nd->flag = 0; } return nd; } void modify( Node *nd, int lf, int rg, int L, int R, int delta ) { if( L <= lf && rg <= R ) { nd->flag += delta; return; } int mid = (lf + rg) >> 1; if( L <= mid ) modify( nd->ls, lf, mid, L, R, delta ); if( R > mid) modify( nd-> rs, mid+1, rg, L, R, delta ); nd->sum = nd->ls->sum + nd->rs->sum + nd->ls->flag*(mid-lf+1) + nd->rs->flag*(rg-mid); } ll query( Node *nd, int lf, int rg, int L, int R ) { if( L <= lf && rg <= R ) { return nd->sum + nd->flag * (rg - lf + 1); } int mid = (lf + rg) >> 1; ll rt = (min(R,rg) - max(lf,L) + 1) * nd->flag; if( L <= mid ) rt += query( nd->ls, lf, mid, L, R ); if( R > mid ) rt += query( nd->rs, mid+1, rg, L, R ); return rt; } int main() { scanf("%d", &n); for( int i = 1; i <= n; i++ ) scanf("%d", &aa[i]); root = build( 1, n ); scanf( "%d", &q ); while(q--) { int yu; scanf("%d", &yu); if(yu == 1) { int a,b,c; scanf("%d%d%d", &a, &b, &c); modify( root, 1, n, a, b, c ); } else { int x; scanf("%d", &x); cout << query( root, 1, n, x, x ) << endl; } } return 0; }
例二
给你一个序列:a1 a2 a3 : : : an,有m 个操作,操作如下:
• modify l r x 将区间[l; r] 中的每个数修改为x
• change l r x 将区间[l; r] 中的每个数加上x
• query l r 询问区间[l; r] 中的和
较上一题略有提升,需要维护多个标记
切记这道题标记的顺序
#include<cstdio>
using namespace std;
typedef long long ll;
const int N = 1000000 + 5;
inline int read() {
int x = 0, f = 1;char ch = getchar();
while( ch < '0' || ch > '9'){if(ch == '-')f=-1;ch=getchar();}
while(ch>='0' && ch <='9'){x = x*10+ch-'0';ch = getchar();}
return x*f;
}
struct Node;
void modify( Node *nd, int lf, int rg, int L, int R, ll value );
void change( Node *nd, int lf, int rg, int L, int R, ll delta );
struct Node{
ll sum;
ll value,delta;
Node *ls, *rs;
int type;
void update(){ sum = ls->sum + rs->sum; }
void pushdown( int lf, int rg ){
if( type == 0 ) return;
int mid = (lf+rg) >> 1;
if( type == 1 ){
change( ls, lf, mid, lf, mid, delta );
change( rs, mid+1, rg, mid+1, rg, delta );
type = 0;
} else{
modify( ls, lf, mid, lf, mid, value );
modify( rs, mid+1, rg, mid+1, rg, value );
type = 0;
}
}
}pool[N*2],*tail = pool,*root;
int m,n,aa[N];
Node *build( int lf, int rg ){
Node *nd = ++tail;
if( lf == rg ){
nd->sum = aa[lf];
nd->type = 0;
} else{
int mid = (lf+rg) >> 1;
nd->ls = build( lf, mid );
nd->rs = build( mid+1, rg);
nd->type = 0;
nd->update();
}
return nd;
}
void modify( Node *nd, int lf, int rg, int L, int R, ll value ){
if( lf >= L && rg <= R ){
nd->sum = (ll)(rg-lf+1)*value;
nd->type = 2; nd->value = value; return ;
}
int mid = (lf+rg) >> 1;
nd->pushdown(lf,rg);
if( L <= mid ) modify( nd->ls, lf, mid, L, R, value );
if( mid < R ) modify( nd->rs, mid+1, rg, L, R, value );
nd->update();
}
void change( Node *nd, int lf, int rg, int L, int R, ll delta ){
if( L <= lf && rg <= R ){
nd->sum += (ll)(rg-lf+1)*delta;
if( nd->type == 0 ){
nd->type = 1;nd->delta = delta;
} else if( nd->type == 1 ) nd->delta += delta;
else if( nd->type == 2 ) nd->value += delta;
return ;
}
int mid = (lf+rg) >> 1;
nd->pushdown(lf,rg);
if( L <= mid ) change( nd->ls, lf, mid, L, R, delta );
if( mid < R ) change( nd->rs, mid+1, rg, L, R, delta );
nd->update();
}
ll query( Node *nd, int lf, int rg,int L, int R ){
if( L <= lf && rg <= R )
return nd->sum;
int mid = (lf+rg) >> 1;
nd->pushdown(lf,rg);
ll rt = 0;
if( mid >= L ) rt += query( nd->ls, lf, mid, L, R );
if( mid < R ) rt+= query( nd->rs, mid+1, rg, L, R );
return rt;
}
int main() {
freopen("setmod.in", "r", stdin);
freopen("setmod.out", "w", stdout);
scanf( "%d%d", &n, &m );
for( int i = 1; i <= n; i++ )
scanf( "%d", aa + i );
root = build( 1, n );
while( m-- ) {
char ss[10];
int l, r, delta, value;
scanf( "%s", ss );
if( ss[0] == 'c' ) {
l = read(); r = read(); delta = read();
change( root, 1, n, l, r, delta );
} else if( ss[0] == 'm' ) {
l = read(); r = read(); value = read();
modify( root, 1, n, l, r, value );
} else {
l = read(); r = read();
printf( "%I64d\n", query( root, 1, n, l, r ) );
}
}
return 0;
}
例三 bzoj5039 序列维护
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1000000 + 5;
long long p,sum[N],f1[N],f2[N],a[100005];
void pushdown( int k, int l, int r ){
int mid = l + r >> 1;
if( f1[k] == 0 && f2[k] == 1 ) return ;
f1[k<<1] = ( f1[k<<1] * f2[k] % p + f1[k] ) % p;
f2[k<<1] = f2[k<<1] * f2[k] % p;
sum[k<<1] = ( ( sum[k<<1] * f2[k] % p ) + ( f1[k] * (LL)(mid-l+1) ) % p ) % p;
f1[k<<1|1] = ( ( f1[k<<1|1] * f2[k] ) % p + f1[k] ) % p;
f2[k<<1|1] = f2[k<<1|1] * f2[k] % p;
sum[k<<1|1] = ( ( sum[k<<1|1] * f2[k] ) % p + ( f1[k] * (LL)(r-mid) ) % p) % p;
f1[k] = 0; f2[k] = 1;
}
void update( int k ){ sum[k] = ( sum[k<<1] + sum[k<<1|1] ) % p; }
void modify( int k, int l, int r, int L, int R, int val ){
if( l >= L && r <= R ){
f1[k] += val; sum[k] += ( val * (LL)( r - l + 1 ) ) % p; sum[k] %= p; return ;
}
pushdown( k, l, r );
int mid = l + r >> 1;
if( L <= mid ) modify( k<<1, l, mid, L, R, val );
if( R > mid ) modify( k<<1|1, mid+1, r, L, R, val );
update( k );
}
void change( int k, int l, int r, int L, int R, int val ){
if( l >= L && r <= R ){
f2[k] *= val; f1[k] *= val; f2[k] %= p; f1[k] %= p; sum[k] *= val; sum[k] %= p; return ;
}
pushdown( k, l, r );
int mid = l + r >> 1;
if( L <= mid ) change( k<<1, l, mid, L, R, val );
if( R > mid ) change( k<<1|1, mid+1, r, L, R, val );
update( k );
}
long long query( int k, int l, int r, int L, int R ){
if( l >= L && r <= R ){
return sum[k]%p;
}
pushdown( k, l, r );
int mid = l + r >> 1; long long res = 0;
if( L <= mid ) res = (res+query( k<<1, l, mid, L, R ))%p;
if( R > mid ) res = (res+query( k<<1|1, mid+1, r, L, R ))%p;
update( k ); return res;
}
int n,q;
void build( int k, int l, int r ){
f1[k] = 0; f2[k] = 1;
if( l == r ){
sum[k] = a[l]%p; return ;
}
int mid = l + r >> 1;
build( k<<1, l, mid );
build( k<<1|1, mid+1, r );
update( k );
}
int main(){
cin>>n>>p;
for( int i = 1; i <= n; i++ ) scanf("%d", &a[i]);
build( 1, 1, n );
scanf("%d", &q);
while( q-- ){
int f,t,g,c;
scanf("%d%d%d", &f, &t, &g);
if( f == 1 ){
scanf("%d", &c);
change( 1, 1, n, t, g, c );
}
if( f == 2 ){
scanf("%d", &c);
modify( 1, 1, n, t, g, c );
}
if( f == 3 ){
printf("%d\n", query( 1, 1, n, t, g ));
}
}
return 0;
}
例四 bzoj1593Hotel 旅馆
题意,01序列,每次操作清空区间,询问di,若有r满足r。。。r+di-1皆为0,选一个最小的填满这个区间,否则不填
01线段树入门题
需要维护的值是区间最大0段,右端开始最多0连续的数量,左端开始最多连续0的数量,这个没有难度
这道题的询问可不要搞成区间问题了,询问的时候优先询问靠左
#include<bits/stdc++.h>
using namespace std;
const int N = 50000 + 5;
int sum[N*4],lmx[N*4],rmx[N*4],siz[N*4],flag[N*4],n,m;
void update( int k ){
sum[k] = max( max( sum[k<<1], sum[k<<1|1] ), lmx[k<<1|1]+rmx[k<<1] );
lmx[k] = lmx[k<<1];
if( sum[k<<1] == siz[k<<1] ) lmx[k] = sum[k<<1] + lmx[k<<1|1];
rmx[k] = rmx[k<<1|1];
if( sum[k<<1|1] == siz[k<<1|1] ) rmx[k] = sum[k<<1|1] + rmx[k<<1];
}
void pushdown( int k, int l, int r ){
if( flag[k] == 1 ){
lmx[k<<1] = rmx[k<<1] = sum[k<<1] = siz[k<<1];
lmx[k<<1|1] = rmx[k<<1|1] = sum[k<<1|1] = siz[k<<1|1];
flag[k<<1] = flag[k<<1|1] = 1;
}
if( flag[k] == 0 ){
lmx[k<<1] = rmx[k<<1] = sum[k<<1] = 0;
lmx[k<<1|1] = rmx[k<<1|1] = sum[k<<1|1] = 0;
flag[k<<1] = flag[k<<1|1] = 0;
}
flag[k] = -1;
}
void build( int k, int l, int r ){
flag[k] = -1; lmx[k] = rmx[k] = sum[k] = siz[k] = r - l + 1;
if( l == r ){ return; }
int mid = l + r >> 1;
build( k<<1, l, mid );
build( k<<1|1, mid+1, r );
update( k );
}
void change( int k, int l, int r, int L, int R, int delta ){
if( l >= L && r <= R ){
lmx[k] = rmx[k] = sum[k] = siz[k] * delta;
flag[k] = delta;
return;
}
pushdown( k, l, r );
int mid = l + r >> 1;
if( mid >= L ) change( k<<1, l, mid, L, R, delta );
if( mid < R ) change( k<<1|1, mid+1, r, L, R, delta );
update( k );
}
int query( int k, int l, int r, int x ){
pushdown( k, l, r );
int mid = l + r >> 1;
if( l == r ) return l;
if( sum[k<<1] >= x ) return query( k<<1, l, mid, x );
if( rmx[k<<1] + lmx[k<<1|1] >= x ) return mid - rmx[k<<1] + 1;
return query( k<<1|1, mid+1, r, x );
}
int main(){
scanf("%d%d", &n, &m);
build( 1, 1, n );
int opt, a, b;
while( m-- ){
scanf("%d%d", &opt, &a );
if( opt == 1 ){
if( sum[1] < a ){ puts("0"); continue; }
int l = query( 1, 1, n, a );
printf("%d\n", l );
change( 1, 1, n, l, l+a-1, 0 );
}else{
scanf("%d", &b );
change( 1, 1, n, a, a+b-1, 1 );
}
}
return 0;
}
例五 bzoj4592 脑洞治疗仪
这道题当时(6月份)时没有写出来,现在不想写了,等到noip完了填坑吧(可能没有机会了)
例六 bzoj3211 花神游历各国
区间开根区间求和
我们发现每一个数开根号的次数比logn还要小
于是维护区间是否为1/0,全是1/0我们就不会下去修改,否则暴力开根,时间复杂度在nlog2n内
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 100000 + 5;
int n,m,siz;
int root,ls[N*4],rs[N*4]; bool flag[N*4];
ll a[N],sum[N*4];
void update( int k ){
sum[k] = sum[ls[k]]+sum[rs[k]];
flag[k] = flag[ls[k]]&&flag[rs[k]];
}
void build( int &nd, int l, int r ){
nd = ++siz;
if( l == r ){
sum[nd] = a[l];
if( sum[nd] == 0 || sum[nd] == 1 ) flag[nd] = 1;
return;
}
int mid = (l+r)>>1;
build( ls[nd], l, mid );
build( rs[nd], mid+1, r );
update(nd);
}
void modify( int nd, int l, int r, int L, int R ){
int mid = (l+r)>>1;
if( l == r ){
sum[nd] = (ll)sqrt(sum[nd]);
if( sum[nd] == 0 || sum[nd] == 1 ) flag[nd] = 1;
return;
}
if( L <= mid && !flag[ls[nd]] ) modify( ls[nd], l, mid, L, R );
if( R > mid && !flag[rs[nd]] ) modify( rs[nd], mid+1, r, L, R );
update(nd);
}
ll query( int nd, int l, int r, int L, int R ){
int mid = (l+r)>>1; ll res=0;
if( l >= L && R >= r ) return sum[nd];
if( L <= mid ) res += query( ls[nd], l, mid, L, R );
if( R > mid ) res += query( rs[nd], mid+1, r, L, R );
return res;
}
int main(){
scanf("%d", &n);
for( int i = 1; i <= n; i++ ) scanf("%d", &a[i]);
build( root, 1, n );
scanf("%d", &m);
for( int i = 1,opt,l,r; i <= m; i++ ){
scanf("%d%d%d", &opt, &l, &r);
if( opt == 1 ) printf("%lld\n", query( root, 1, n, l, r ));
if( opt == 2 ) modify( root, 1, n, l, r );
}
return 0;
}
例七 tyvj1518
这道题比较难
#include<iostream>
#include<cstdio>
typedef long long ll;
#define inf 1e18
using namespace std;
const int N = 150005;
int n,m,a[N],ls[N*4],rs[N*4],id=0,root; char ch[5];
ll mx[N*4],f1[N*4],f2[N*4],m_x[N*4],f_1[N*4],f_2[N*4];
void update( int k ){
mx[k] = max(mx[ls[k]],mx[rs[k]]);
m_x[k] = max(m_x[ls[k]],m_x[rs[k]]);
}
void pushdown( int k ){
m_x[ls[k]] = max(m_x[ls[k]],max(f_2[k],mx[ls[k]]+f_1[k]));
if( f2[ls[k]] == -inf ) f_1[ls[k]] = max(f_1[ls[k]],f1[ls[k]]+f_1[k]);
else f_2[ls[k]] = max(f_2[ls[k]],f2[ls[k]]+f_1[k]);
if( f1[k] ){
if( f2[ls[k]] != -inf ) f2[ls[k]] += f1[k];
else f1[ls[k]] += f1[k];
mx[ls[k]] += f1[k];
}
if( f2[k] != -inf ){
mx[ls[k]] = f2[ls[k]] = f2[k];
f1[ls[k]] = 0;
}
f_1[ls[k]] = max(f_1[ls[k]],f1[ls[k]]);
f_2[ls[k]] = max(f_2[ls[k]],max(f2[k],f_2[k]));
//======================================================================================================
m_x[rs[k]] = max(m_x[rs[k]],max(f_2[k],mx[rs[k]]+f_1[k]));
if( f2[rs[k]] == -inf ) f_1[rs[k]] = max(f_1[rs[k]],f1[rs[k]]+f_1[k]);
else f_2[rs[k]] = max(f_2[rs[k]],f2[rs[k]]+f_1[k]);
if( f1[k] ){
if( f2[rs[k]] != -inf ) f2[rs[k]] += f1[k];
else f1[rs[k]] += f1[k];
mx[rs[k]] += f1[k];
}
if( f2[k] != -inf ){
mx[rs[k]] = f2[rs[k]] = f2[k];
f1[rs[k]] = 0;
}
f_1[rs[k]] = max(f_1[rs[k]],f1[rs[k]]);
f_2[rs[k]] = max(f_2[rs[k]],max(f2[k],f_2[k]));
//======================================================================================================
f_1[k] = f1[k] = 0;
f_2[k] = f2[k] = -inf;
}
void build( int &k, int l, int r ){
k = ++id; f1[k] = f_1[k] = 0; f2[k] = f_2[k] = -inf;
if( l == r ){
mx[k] = m_x[k] = (ll)a[l]; return;
}
int mid = (l+r)>>1;
build(ls[k],l,mid); build(rs[k],mid+1,r);
update(k);
}
void change( int k, int l, int r, int L, int R, ll x, int type ){
if( l != r ) pushdown(k);
if( L <= l && r <= R ){
if( !type ) mx[k] += x, f1[k] += x, f_1[k] += x;
else f2[k] = f_2[k] = mx[k] = x;
m_x[k] = max(mx[k],m_x[k]);
return;
}
int mid = (l+r)>>1;
if( mid >= L ) change( ls[k], l, mid, L, R, x, type );
if( mid < R ) change( rs[k], mid+1, r, L, R, x, type );
update(k);
}
ll query( int k, int l, int r, int L, int R, int type ){
if( l != k ) pushdown(k);
if( L <= l && R >= r ){ return type ? m_x[k] : mx[k]; }
int mid = (l+r)>>1; ll res = -inf;
if( mid >= L ) res = max(res,query(ls[k],l,mid,L,R,type));
if( mid < R ) res = max(res,query(rs[k],mid+1,r,L,R,type));
return res;
}
int main(){
scanf("%d",&n);
for( int i = 1; i <= n; i++ ) scanf("%d", &a[i]);
build(root,1,n); scanf("%d", &m);
for( int i = 1,l,r,x; i <= m; i++ ){
scanf("%s", ch);
if( ch[0] == 'Q' ){ scanf("%d%d", &l, &r); printf("%lld\n",query(root,1,n,l,r,0));}
if( ch[0] == 'A' ){ scanf("%d%d", &l, &r); printf("%lld\n",query(root,1,n,l,r,1));}
if( ch[0] == 'P' ){ scanf("%d%d%d", &l, &r, &x); change(root,1,n,l,r,(ll)x,0);}
if( ch[0] == 'C' ){ scanf("%d%d%d", &l, &r, &x); change(root,1,n,l,r,(ll)x,1);}
}
return 0;
}
树上应用——维护dfs序
顾名思义,dfs序是用我们dfs一棵树的顺序决定的,in表示开始访问这个点的时间,out是出这个点的时间,出去的时候不需要time++,这是鸡肋的
所以( in[x], out[x] )这个区间就是x的子树,dfs序在处理树的子树问题时发挥着重要的作用
例八 bzoj4551
对一个点打标记
询问某个节点打了标记的最近的祖先
你对每一个点打标记相当于就是可能修改了这个点的子树的答案
#include<bits/stdc++.h>
using namespace std;
const int N = 1000000 + 5;
int last[N],cnt,ans,tot=0,n,q,root=1,in[N],out[N],dep[N];
struct Edge{ int to,next; }e[N*2];
void insert( int u, int v ){ e[++cnt].to = v; e[cnt].next = last[u]; last[u] = cnt; }
void dfs( int x, int f ){
in[x] = ++tot; dep[x] = dep[f]+1;
for( int i = last[x]; i; i = e[i].next )
if( e[i].to ^ f )
dfs( e[i].to, x );
out[x] = tot;
}
int mx[N];
void change( int k, int l, int r, int L, int R, int x ){
if( l >= L && r <= R ){
if( dep[mx[k]] < dep[x] ) mx[k] = x; return;
}
int mid = l + r >> 1;
if( L <= mid ) change( k<<1, l, mid, L, R, x );
if( R > mid ) change( k<<1|1, mid+1, r, L, R, x );
}
void query( int k, int l, int r, int x ){
if( dep[ans] < dep[mx[k]] ) ans = mx[k];
if( l == r ) return ;
int mid = l + r >> 1;
if( x <= mid ) query( k<<1, l, mid, x );
if( x > mid ) query( k<<1|1, mid+1, r, x );
}
int main(){
cin>>n>>q;
for( int i = 1,u,v; i < n; i++ ){
scanf("%d%d", &u, &v);
insert( u, v ); insert( v, u );
}
dep[1] = 1; dfs( 1, 0 );
for( int i = 1; i <= tot; i++ ) mx[i] = 1;
// build( root, 1, tot );
while( q-- ){
char opt[5]; int x;
scanf("%s", opt); scanf("%d", &x);
if( opt[0] == 'C' ) change( root, 1, n, in[x], out[x], x );
else{
ans = 0;
query( root, 1, n, in[x]);
printf("%d\n", ans);
}
}
return 0;
}
例九 bzoj1782
一棵树,有一些牛从根节点出发,到了之后下一头牛出发,求每头牛能遇到的牛的个数
我们每到一个节点,相当于我们到它的子树答案++
#include<bits/stdc++.h>
using namespace std;
const int N = 1000000 + 5;
int last[N],cnt,ans,tot=0,n,q,root=1,in[N],out[N],dep[N],p[N],flag[N],sum[N];
struct Edge{ int to,next; }e[N*2];
void insert( int u, int v ){ e[++cnt].to = v; e[cnt].next = last[u]; last[u] = cnt; }
void dfs( int x, int f ){
in[x] = ++tot; dep[x] = dep[f] + 1;
for( int i = last[x]; i; i = e[i].next )
if( e[i].to ^ f )
dfs( e[i].to, x );
out[x] = tot;
}
void pushdown( int k, int l, int r ){
if( flag[k] ){
int mid = l + r >> 1;
sum[k<<1] += flag[k] * ( mid - l + 1 ); flag[k<<1] += flag[k];
sum[k<<1|1] += flag[k] * ( r - mid ); flag[k<<1|1] += flag[k];
flag[k] = 0;
}
}
void update( int k ){
sum[k] = sum[k<<1] + sum[k<<1|1];
}
void modify( int k, int l, int r, int L, int R, int val ){
if( l >= L && r <= R ){
flag[k] += val; sum[k] += ( r - l + 1 ); return;
}
int mid = l + r >> 1;
pushdown( k, l, r );
if( L <= mid ) modify( k<<1, l, mid, L, R, val );
if( R > mid ) modify( k<<1|1, mid+1, r, L, R, val );
update( k );
}
int query( int k, int l, int r, int L, int R ){
if( l >= L && R >= r ){
return sum[k];
}
pushdown( k, l, r );
int mid = l + r >> 1, res = 0;
if( L <= mid ) res += query( k<<1, l, mid, L, R );
if( R > mid ) res += query( k<<1|1, mid+1, r, L, R );
return res;
}
int main(){
cin>>n;
for( int i = 1,u,v; i < n; i++ ){
scanf("%d%d", &u, &v);
insert( u, v ); insert( v, u );
}
for( int i = 1; i <= n; i++ ) scanf("%d", &p[i]);
dep[1] = 1; dfs( 1, 0 );
for( int i = 1; i <= n; i++ ){
int res = query( root, 1, n, in[p[i]], in[p[i]] );
modify( root, 1, n, in[p[i]], out[p[i]], 1 );
printf("%d\n", res);
}
return 0;
}
例十 bzoj3306
换根,子树最小,单点修改
说起来简单实际神烦,考虑补集转化
#include<bits/stdc++.h>
using namespace std;
const int N = 1000000 + 5;
int last[N],cnt,q,ans,tot=0,n,root=1,in[N],out[N],dep[N],p[N],flag[N],sum[N],w[N],id,anc[N][20];
struct Edge{ int to,next; }e[N*2];
void insert( int u, int v ){ e[++cnt].to = v; e[cnt].next = last[u]; last[u] = cnt; }
void dfs( int x, int f ){
in[x] = ++tot; dep[x] = dep[f] + 1; anc[x][0] = f;
for( int i = 1; i <= 19; i++ ) anc[x][i] = anc[anc[x][i-1]][i-1];
for( int i = last[x]; i; i = e[i].next )
if( e[i].to ^ f )
dfs( e[i].to, x );
out[x] = tot;
}
void pushdown( int k, int l, int r ){
if( flag[k] ){
sum[k<<1] = flag[k]; flag[k<<1] = flag[k];
sum[k<<1|1] = flag[k]; flag[k<<1|1] = flag[k];
flag[k] = 0;
}
}
void update( int k ){
sum[k] = min( sum[k<<1], sum[k<<1|1] );
}
void modify( int k, int l, int r, int L, int R, int val ){
if( l >= L && r <= R ){
flag[k] = val; sum[k] = val; return;
}
int mid = l + r >> 1;
pushdown( k, l, r );
if( L <= mid ) modify( k<<1, l, mid, L, R, val );
if( R > mid ) modify( k<<1|1, mid+1, r, L, R, val );
update( k );
}
int query( int k, int l, int r, int L, int R ){
if( l >= L && R >= r ){
return sum[k];
}
pushdown( k, l, r );
int mid = l + r >> 1, res = 2147483647;
if( L <= mid ) res = min( res, query( k<<1, l, mid, L, R ) );
if( R > mid ) res = min( res, query( k<<1|1, mid+1, r, L, R ) );
return res;
}
int up( int x, int p ){
for( int i = 19; i >= 0; i-- )
if( (1<<i) & p ) x = anc[x][i];
return x;
}
int main(){
cin>>n>>q;
for( int i = 1,u,v; i <= n; i++ ){
scanf("%d%d", &u, &v); if( u == 0 ) id = i;
else insert( u, i ), insert( i, u );
w[i] = v;
}
dep[id] = 1; dfs( id, 0 );
for( int i = 1; i <= n; i++ ) modify( root, 1, n, in[i], in[i], w[i] );
for( int i = 1; i <= q; i++ ){
char opt[5]; int x, y;
scanf("%s", opt);
if( opt[0] == 'V' ){
scanf("%d%d", &x, &y);
modify( root, 1, n, in[x], in[x], y );
}if( opt[0] == 'E' ){
scanf("%d", &id);
}if( opt[0] == 'Q' ){
scanf("%d", &x);
if( x == id ) printf("%d\n", query( root, 1, n, 1, n ) );
else if( in[x] <= in[id] && out[id] <= out[x] ){
int y = up( id, dep[id]-dep[x]-1 );
printf("%d\n", min( query( root, 1, n, 1, in[y]-1 ), query( root, 1, n, out[y]+1, n) ) );
}else printf("%d\n", query( root, 1, n, in[x], out[x] ) );
}
}
return 0;
}
树上应用——树链剖分
树链剖分这个东西好像好多都能用LCT
可以和dfs序一起使用,注意要在树链剖分时计入度出度(这种题太烦了现在还没调出来)
例十一 加帕里的聚会
加帕里公园里有n个区域,n-1条道路将它们连接到了一起,形成了一个树的结构。开始时,第i个区域有Ai个friends,但是由于砂之星的作用,有时从x区域到y区域的简单路径上的所有区域的friends数量都会增加v,有时从x区域到y区域的简单路径上所有区域的friends数量都会变成v。
有时,从x区域到y区域的简单路径上所有区域的friends想要聚会,聚会时需要从所有参加聚会的friends中选择一部分作为staff。加帕里的friends都很喜欢质数,因此她们希望staff和非staff的参与者的数量都是质数。
请你告诉她们,每次聚会是否存在一种方案满足她们的要求
裸的树链剖分,支持的操作如例二
例十二 bzoj1984
一棵树,支持链上修改,链上增减,u到v的最大边
这道题修改操作与上一道题相同,但是这道题是边权,我们就要把边权下放到点权
实际计算的时候仍然是当做点来做的,把一条边的权放到下面的点上面
#include<iostream>
#include<cstring>
#include<cstdio>
#define inf 0x7fffffff
const int N = 100000 + 5;
using namespace std;
int cnt=1,n,sz; char ch[10];
int pos[N],bel[N],last[N],dep[N],siz[N],anc[N][20],id[N];
int ls[N<<2],rs[N<<2],mx[N<<2],l[N<<2],r[N<<2],mf[N<<2],af[N<<2],root,idd;
struct Edge{int to,next,v;}e[N<<1];
void insert(int u,int v,int w){
e[++cnt].to = v; e[cnt].next = last[u]; e[cnt].v = w; last[u] = cnt;
e[++cnt].to = u; e[cnt].next = last[v]; e[cnt].v = w; last[v] = cnt;
}
void build( int &k, int L, int R ){
k = ++idd; l[idd] = L; r[idd] = R; mf[k] = -1;
if( L == R ) return; int mid = (L+R)>>1;
build( ls[k], L, mid ); build( rs[k], mid+1, R );
}
void update( int k ){
mx[k] = max(mx[ls[k]],mx[rs[k]]);
}
void pushdown( int k ){
if( l[k] == r[k] ) return;
if( mf[k] != -1 ){
af[ls[k]] = af[rs[k]] = 0;
mx[ls[k]] = mx[rs[k]] = mf[ls[k]] = mf[rs[k]] = mf[k];
mf[k] = -1;
} if( af[k] ){
mx[ls[k]] += af[k]; mx[rs[k]] += af[k];
if( mf[ls[k]] != -1 ) mf[ls[k]] += af[k]; else af[ls[k]] += af[k];
if( mf[rs[k]] != -1 ) mf[rs[k]] += af[k]; else af[rs[k]] += af[k];
af[k] = 0;
}
}
void change( int k, int x, int y, int v ){
pushdown(k);
if( x == l[k] && y == r[k] ){ mx[k] = mf[k] = v; return;}
int mid = (l[k]+r[k])>>1;
if( mid >= y ) change(ls[k],x,y,v);
else if( mid < x ) change(rs[k],x,y,v);
else {
change(ls[k],x,mid,v);
change(rs[k],mid+1,y,v);
}
update(k);
}
void modify( int k, int x, int y, int v ){
pushdown(k);
if( x == l[k] && y == r[k] ){ mx[k] += v; af[k] = v; return;}
int mid = (l[k]+r[k])>>1;
if( mid >= y ) modify(ls[k],x,y,v);
else if( mid < x ) modify(rs[k],x,y,v);
else {
modify(ls[k],x,mid,v);
modify(rs[k],mid+1,y,v);
}
update(k);
}
int query( int k, int x, int y ){
pushdown(k);
if( l[k] == x && r[k] == y ) return mx[k];
int mid = (l[k]+r[k])>>1;
if( mid >= y ) return query(ls[k],x,y);
else if( mid < x ) return query(rs[k],x,y);
else return max(query(ls[k],x,mid),query(rs[k],mid+1,y));
}
void solve_change( int x, int f, int v ){
while( bel[x] != bel[f] ){
change(root,pos[bel[x]],pos[x],v);
x = anc[bel[x]][0];
}
if( pos[f]+1 <= pos[x] ) change(root,pos[f]+1,pos[x],v);
}
void solve_modify( int x, int f, int v ){
while( bel[x] != bel[f] ){
modify(root,pos[bel[x]],pos[x],v);
x = anc[bel[x]][0];
}
if( pos[f]+1 <= pos[x] ) modify(root,pos[f]+1,pos[x],v);
}
int solve_query( int x, int f ){
int res = -inf;
while( bel[x] != bel[f] ){
res = max(res,query(root,pos[bel[x]],pos[x]));
x = anc[bel[x]][0];
}
if( pos[f]+1 <= pos[x] ) res = max(res,query(root,pos[f]+1,pos[x]));
return res;
}
void dfs1( int x, int f ){
siz[x] = 1;
for( int p = 1; p <= 16; p++ )
if( (1<<p) <= dep[x] ) anc[x][p] = anc[anc[x][p-1]][p-1];
else break;
for( int i = last[x]; i; i = e[i].next )
if( e[i].to != f ){
dep[e[i].to] = dep[x]+1;
anc[e[i].to][0] = x;
dfs1(e[i].to,x);
siz[x] += siz[e[i].to];
}
}
void dfs2( int x, int chain ){
int k = 0; bel[x] = chain; pos[x] = ++sz;
for( int i = last[x]; i; i = e[i].next )
if( dep[e[i].to] > dep[x] ){
if( siz[e[i].to] > siz[k] ) k = e[i].to;
} else{ id[i>>1] = x; modify(1,pos[x],pos[x],e[i].v); }
if( !k ) return; dfs2(k,chain);
for( int i = last[x]; i; i = e[i].next )
if( dep[e[i].to] > dep[x] && e[i].to != k ) dfs2(e[i].to,e[i].to);
}
int lca( int x, int y ){
if( dep[x] < dep[y] ) swap(x,y);
int t = dep[x]-dep[y];
for( int p = 0; p <= 16; p++ ) if( (1<<p)&t ) x = anc[x][p];
if( x == y ) return x;
for( int p = 16; p >= 0; p-- ) if( anc[x][p] != anc[y][p] ) x = anc[x][p], y = anc[y][p];
return anc[x][0];
}
int main(){
scanf("%d", &n);
for( int i = 1, u, v, w; i < n; i++ ) scanf("%d%d%d", &u, &v, &w), insert( u, v, w );
build( root, 1, n ); dfs1(1,0); dfs2(1,1);
while( 1 ){
int u,v,w,f;
scanf("%s", ch);
switch(ch[1]){
case 't':return 0;break;
case 'd':scanf("%d%d%d",&u,&v,&w);f=lca(u,v); solve_modify(u,f,w);solve_modify(v,f,w);break;
case 'o':scanf("%d%d%d",&u,&v,&w);f=lca(u,v); solve_change(u,f,w);solve_change(v,f,w);break;
case 'h':scanf("%d%d",&u,&w);change(1,pos[id[u]],pos[id[u]],w);break;
case 'a':scanf("%d%d",&u,&v);f=lca(u,v);printf("%d\n",max(solve_query(u,f),solve_query(v,f)));break;
}
}
}
例十三 bzoj3694 最短路
给出一个n个点m条边的无向图,n个点的编号从1~n,定义源点为1。定义最短路树如下:从源点1经过边集T到任意一点i有且仅有一条路径,且这条路径是整个图1到i的最短路径,边集T构成最短路树。 给出最短路树,求对于除了源点1外的每个点i,求最短路,要求不经过给出的最短路树上的1到i的路径的最后一条边。
#include<iostream>
#include<cstring>
#include<cstdio>
#define inf 0x7fffffff
const int N = 100000 + 5;
using namespace std;
int cnt=1,n,m,sz;
int pos[N],bel[N],last[N],dep[N],dis[N],siz[N],anc[N][20],u[N],v[N],w[N];
int ls[N*4],rs[N*4],mx[N*4],sum[N*4],l[N*4],r[N*4],va[N*4],type[N*4],root,idd,cnt1=1;
struct Edge{int to,next,v;}e[N<<1];
void insert(int u,int v, int w ){
e[++cnt].to = v; e[cnt].next = last[u]; e[cnt].v = w; last[u] = cnt;
e[++cnt].to = u; e[cnt].next = last[v]; e[cnt].v = w; last[v] = cnt;
}
void dfs1( int x, int f ){
siz[x] = 1;
for( int p = 1; p <= 11; p++ )
if( (1<<p) <= dep[x] ) anc[x][p] = anc[anc[x][p-1]][p-1];
else break;
for( int i = last[x]; i; i = e[i].next )
if( e[i].to != f ){
dep[e[i].to] = dep[x]+1;
dis[e[i].to] = dis[x]+e[i].v;
anc[e[i].to][0] = x;
dfs1(e[i].to,x);
siz[x] += siz[e[i].to];
}
}
void dfs2( int x, int chain ){
int k = 0; bel[x] = chain; pos[x] = ++sz;
for( int i = last[x]; i; i = e[i].next )
if( dep[e[i].to] > dep[x] )
if( siz[e[i].to] > siz[k] ) k = e[i].to;
if( !k ) return; dfs2(k,chain);
for( int i = last[x]; i; i = e[i].next )
if( dep[e[i].to] > dep[x] && e[i].to != k ) dfs2(e[i].to,e[i].to);
}
int lca( int x, int y ){
if( dep[x] < dep[y] ) swap(x,y);
int t = dep[x]-dep[y];
for( int p = 0; p <= 11; p++ ) if( (1<<p)&t ) x = anc[x][p];
if( x == y ) return x;
for( int p = 11; p >= 0; p-- ) if( anc[x][p] != anc[y][p] ) x = anc[x][p], y = anc[y][p];
return anc[x][0];
}
void build( int &k, int L, int R ){
k = ++idd; l[k] = L; r[k] = R; type[k] = inf;
if( L == R ){ va[k] = inf; return; }
int mid = (L+R)>>1;
build( ls[k], L, mid ); build( rs[k], mid+1, R );
}
void pushdown( int k ){
if( l[k] == r[k] || type[k] == inf ) return;
type[ls[k]] = min(type[k],type[ls[k]]);
type[rs[k]] = min(type[k],type[rs[k]]);
if( l[ls[k]] == r[ls[k]] ) va[ls[k]] = min( va[ls[k]], type[k] );
if( l[rs[k]] == r[rs[k]] ) va[rs[k]] = min( va[rs[k]], type[k] );
type[k] = inf;
}
void modify( int k, int x, int y, int v ){
pushdown(k);
if( l[k] == x && r[k] == y ){
type[k] = min(type[k],v);
if( l[k] == r[k] ) va[k] = min(v,va[k]);
return;
}
int mid = (l[k]+r[k])>>1;
if( y <= mid ) modify( ls[k], x, y, v );
else if( x > mid ) modify( rs[k], x, y, v );
else{ modify(ls[k],x,mid,v); modify(rs[k],mid+1,y,v); }
}
int query( int k, int x ){
pushdown(k);
if( l[k] == r[k] ) return va[k];
int mid = (l[k]+r[k])>>1;
if( x <= mid ) return query(ls[k],x);
else return query(rs[k],x);
}
void solve_modify( int x, int f, int v ){
while( bel[x] != bel[f] ){
modify( root, pos[bel[x]], pos[x], v );
x = anc[bel[x]][0];
}
if( x != f ) modify( root, pos[f]+1, pos[x], v );
}
int main(){
scanf("%d%d", &n, &m);
for( int i = 1,flag; i <= m; i++ ){
scanf("%d%d%d%d", &u[cnt1], &v[cnt1], &w[cnt1], &flag);
if( !flag ) cnt1++; else insert(u[cnt1],v[cnt1],w[cnt1]);
}
dfs1(1,0); dfs2(1,1); build( root, 1, n );
for( int i = 1; i < cnt1; i++ ){
int t = lca(u[i],v[i]);
solve_modify( u[i], t, dis[u[i]]+dis[v[i]]+w[i] );
solve_modify( v[i], t, dis[u[i]]+dis[v[i]]+w[i] );
}
for( int i = 2; i <= n; i++ ){
int d = query( root, pos[i] );
if( d != inf ) printf("%d ", d-dis[i]);
else printf("-1 ");
}
return 0;
}
值域操作——值域线段树
例十四 kth
求区间第k大
这是主席树的裸题,同时也可以用线段树套值域线段树实现,具体就是把一般线段树维护的是一段区间的值,而这里面一个区间相当于维护的是一棵值域线段树
同样的道理,如果需要实现插入就用替罪羊树套值域线段树,见bzoj3065
线段树其他应用
例十五 area
给出n 个矩形,求它们的面积并.
更准确一点,每个矩形将给出它的左上角和右下角的位置:x1; y1; x2; y2
这四个数都是整数且满足x1 x2; y1 y2.
我们需要你求:
area =j f(x; y) 2 Z Z j 9 a rect: s:t: x1 x x2 and y1 y y2g j
扫描线裸题 题解 by idy002(比我的详细多了)
扫描线,对于一个举行(x1,y1,x2,y2),将它看成两个事件:在x1 这个时间将(y1,y2) 这个区间加一,在x2+1 这个时间将(y1,y2) 这个区间减一。
这样,我们遍历整个时间,并在执行完这个时间的操作后看看有多少位置非0, 将其数量加到答案中,就完了,当然时间不能傻傻地一个一个枚,因为关键
的时间点最多2n 个,其它时候面积是没有变的,所以要一段一段地算。至于怎么用线段树实现那么查看有多少个非零的位置,需要注意对于任何一
个减一操作,前面一定有一个和它一样的加一操作,就只需要维护一下每个节点被完全覆盖的次数。再用另一个来统计子树中的那些修改导致这个节点还有
多少个非零。有点像标记永久化(我们讲的第二种区间修改的写法)。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 100000 + 10;
typedef long long ll;
inline int read() {
int x = 0, f = 1;char ch = getchar();
while( ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
while( ch >= '0' && ch <= '9' ){x = x*10+ch-'0';ch = getchar();}
return x*f;
}
struct Event{
int type;
int time;
int lf, rg;
Event(){}
Event( int type, int time, int lf, int rg )
:type(type),time(time),lf(lf),rg(rg){}
};
bool operator<( const Event &r, const Event &s ) {
return r.time < s.time;
}
struct Node{
int cnt,sum;
Node *ls, *rs;
int query( int lf, int rg ){
return cnt? rg - lf + 1 : sum;
}
void update( int lf, int rg ){
int mid = (lf + rg) >> 1;
sum = ls->query(lf,mid) + rs->query(mid+1,rg);
}
}pool[N*2], *tail = pool, *root;
int n,total;
Event events[N*2];
Node *build( int lf, int rg ){
Node *nd = ++tail;
if( lf == rg ) nd->cnt = nd->sum = 0;
else{
int mid = (lf + rg) >> 1;
nd->ls = build( lf, mid );
nd->rs = build( mid+1, rg );
nd->sum = nd->cnt = 0;
}
return nd;
}
void modify( Node *nd, int lf, int rg, int L, int R, int delta ) {
if( L <= lf && R >= rg ){
nd->cnt += delta;
return;
}
int mid = (lf + rg) >> 1;
if( L <= mid ) modify( nd->ls, lf, mid, L, R, delta );
if( mid < R ) modify( nd->rs, mid+1, rg, L, R, delta );
nd->update(lf,rg);
}
int main(){
freopen("area.in","r",stdin);
freopen("area.out","w",stdout);
n = read(); int yu = n; int gu = 100000;
for( int i = 1; i <= n; i++ ){
int x1,x2,y1,y2;
x1 = read(); y1 = read(); x2 = read(); y2 = read();
events[total++] = Event( 1, x1, y1, y2 );
events[total++] = Event( 2, x2+1, y1, y2 );
if( y2 > yu ) yu = y2;
if( y1 < gu ) gu = y1;
}
std::sort( events, events+total );
root = build( gu, yu );
ll ans = 0;
for( int i = 0,j; i < total; i = j + 1 ){
for( j = i; j + 1 < total && events[j+1].time == events[i].time; j++ );
for( int k = i; k <= j; k++ )
modify( root, gu, yu, events[k].lf, events[k].rg, events[k].type==1?1:-1);
if( j+1 != total )
ans += (ll)root->query(gu,yu)*(events[j+1].time - events[i].time);
}
printf("%I64d",ans);
return 0;
}
例十六 [Usaco2013 Dec]Optimal Milking
线段树实现dp,太水不想说了
#include <bits/stdc++.h>
using namespace std;
const int N = 40000 + 5; long long ans;
int tl[N<<2],tr[N<<2],ta[N<<2],tn[N<<2],n,d,a[N];
void update( int k ){
tl[k] = max( tl[k<<1] + tn[k<<1|1], max( ta[k<<1] + tn[k<<1|1], tl[k<<1] + tl[k<<1|1] ) );
tr[k] = max( tn[k<<1] + tr[k<<1|1], max( tn[k<<1] + ta[k<<1|1], tr[k<<1] + tr[k<<1|1] ) );
tn[k] = max( tn[k<<1] + tn[k<<1|1], max( tn[k<<1] + tl[k<<1|1], tr[k<<1] + tn[k<<1|1] ) );
ta[k] = max( ta[k<<1] + tr[k<<1|1], max( tl[k<<1] + tr[k<<1|1], tl[k<<1] + ta[k<<1|1] ) );
}
void build( int k, int l, int r ){
if( l == r ){
ta[k] = a[l]; return;
}
int mid = l + r >> 1;
build( k<<1, l, mid );
build( k<<1|1, mid+1, r );
update( k );
}
void change( int k, int l, int r, int x, int val ){
if( l == r ){ ta[k] = val; return; }
int mid = l + r >> 1;
if( x <= mid ) change( k<<1, l, mid, x, val );
if( x > mid ) change( k<<1|1, mid+1, r, x, val );
update( k );
}
int main(){
scanf( "%d%d", &n, &d );
for( int i = 1; i <= n; i++ ) scanf( "%d", &a[i] );
build( 1, 1, n );
while( d-- ){
int i,m;
scanf( "%d%d", &i, &m );
change( 1, 1, n, i, m );
ans += max( max( tl[1], tr[1] ), max( tn[1], ta[1] ) );
}
printf( "%lld\n", ans );
return 0;
}

浙公网安备 33010602011771号