数据结构总结——线段树

此博客仅为总结,不适合新手

线段树

线段树(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 序列维护

JYY 现在有一个长度为 N 的序列 a1,a2,…,aN,有如下三种操作:
1、 把数列中的一段数全部乘以一个值;
2、 把数列中的一段数全部加上一个值;
3、 询问序列中的一段数的和。
从现在开始就是数组版了( k<<1 左儿子 k<<1|1右儿子 )
我记得有一道树链剖分也是同样的操作
这道题同样也是两个标记
先处理乘法标记再处理加法标记
#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的路径的最后一条边。

首先把最短路径树建出来,把不在这棵树上的边存下来,然后对答案进行更新
具体是:
开始都是inf的距离,对于一条边u->v,长度为w,设t=lca(u,v),对于t-v链上所有点x,可通过root -> t -> u -> v -> x 到达x,距离为dis[u]+w+dis[v]-dis[x],然后依次更新,查询就直接查单点就可以了
#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;
}

 

 

posted @ 2017-10-21 16:34  Leokery  阅读(598)  评论(0编辑  收藏  举报