数据结构总结——线段树
此博客仅为总结,不适合新手
线段树
线段树(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; }