线段树的认识,可以参考哔哔丽丽的视频。
(1)单点曽减,区间求和
飞翔
#include#define lson l,m,rt<<1///左儿子 #define rson m+1,r,rt<<1|1///右儿子 const int maxn = 55555; int sum[maxn<<2]; void PushUP(int rt)///上一个的值由两个儿子相加 { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt)///建立 { if(l==r) { scanf("%d",&sum[rt]);///在建立的过程中赋值 return ; } int m=(l+r)>>1; build(lson); build(rson); PushUP(rt); } void update(int p,int add,int l,int r,int rt) ///p点增减add { if(l==r) { sum[rt]+=add; return ; } int m=(l+r)>>1; if(p<=m) update(p,add,lson); else update(p,add,rson); PushUP(rt); } ///LR区间求和 int query(int L,int R,int l,int r,int rt) { if(L <= l && r<=R) return sum[rt]; int m=(l+r)>>1; int ret=0; if(L <= m ) ret+=query(L,R,lson); if(R > m) ret+=query(L,R,rson); return ret; } int main( ) { int a,b,t,n; scanf("%d",&t); for(int cas=1 ; cas<=t ; cas++) { printf("Case %d:\n",cas); scanf("%d",&n); build(1,n,1); char op[10]; while(scanf("%s",op)) { if(op[0]=='E') break; int a,b; scanf("%d%d",&a,&b); if(op[0]=='S') update(a,-b,1,n,1); else if(op[0]=='Q') printf("%d\n",query(a,b,1,n,1)); else update(a,b,1,n,1); } } return 0; }
(2)单点替换,区间最值
飞翔
#include#include using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int manx = 222222; int MAX[manx<<2]; void PushUP(int rt)///父亲的值由儿子来得出 { MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]); } void build(int l,int r,int rt) { if ( l==r ) { scanf("%d",&MAX[rt]); return ; } int m = (l+r) >> 1; build(lson); build(rson); PushUP(rt); } void update(int p,int sc,int l,int r,int rt) { ///p点变为sc if (l==r) { MAX[rt]=sc; return ; } int m=(l+r)>>1; if(p <= m ) update(p,sc,lson); else update(p,sc,rson); PushUP(rt); } int query(int L,int R,int l,int r,int rt) { ///L到R的最大值 if(L <= l&&r<= R) return MAX[rt]; int m=(l+r)>>1; int ret=0; if(L<=m) ret=max(ret,query(L,R,lson)); if(R>m) ret=max(ret,query(L,R,rson)); return ret; } int main( ) { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { build(1,n,1); while(m--) { char op[2]; int a,b; scanf("%s%d%d",op,&a,&b); if(op[0]=='Q') printf("%d\n",query(a,b,1,n,1)); else update(a,b,1,n,1); } } return 0; }
(3)线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息) ,区间覆盖
飞翔
#include#include using namespace std; #define lson l , m , rt << 1 #define rson m + 1 , r , rt << 1 | 1 const int maxn = 111111; int h , w , n; int col[maxn<<2]; int sum[maxn<<2]; void PushUp(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void PushDown(int rt,int m) { if (col[rt]) { col[rt<<1] = col[rt<<1|1] = col[rt]; sum[rt<<1] = (m - (m >> 1)) * col[rt]; sum[rt<<1|1] = (m >> 1) * col[rt]; col[rt] = 0; } } void build(int l,int r,int rt) { col[rt] = 0; sum[rt] = 1; if (l == r) return ; int m = (l + r) >> 1; build(lson); build(rson); PushUp(rt); } void update(int L,int R,int c,int l,int r,int rt) { if (L <= l && r <= R) { col[rt] = c; sum[rt] = c * (r - l + 1); return ; } PushDown(rt , r - l + 1); int m = (l + r) >> 1; if (L <= m) update(L , R , c , lson); if (R > m) update(L , R , c , rson); PushUp(rt); } int main() { int T , n , m; scanf("%d",&T); for (int cas = 1 ; cas <= T ; cas ++) { scanf("%d%d",&n,&m); build(1 , n , 1); while (m --) { int a , b , c; scanf("%d%d%d",&a,&b,&c); update(a , b , c , 1 , n , 1); } printf("Case %d: The total value of the hook is %d.\n",cas , sum[1]); } return 0; }
(4)线段树功能:update:成段增减 query:区间求和
飞翔
#include#define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define ll long long const int manx = 100011; ll sum[manx<<2],la[manx<<2]; void PushUP(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void build(int l,int r,int rt) { la[rt]=0; if(l==r) { scanf("%lld",&sum[rt]); return ; } int m = (l+r) >> 1; build(lson); build(rson); PushUP(rt); } void PushDown(int rt,int m) { if(la[rt]) { la[rt<<1]+=la[rt]; la[rt<<1|1]+=la[rt]; sum[rt<<1]+=la[rt]*(m-(m>>1)); sum[rt<<1|1]+=la[rt]*(m>>1); la[rt]=0; } } void update(int L,int R,int c,int l,int r,int rt) { if(L<=l&&r<=R) { la[rt]+=c; sum[rt]+=(ll)c*(r-l+1); return ; } PushDown(rt,r-l+1); int m=(l+r)>>1; if(L<=m) update(L,R,c,lson); if(R>m) update(L,R,c,rson); PushUP(rt); } ll query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R) { return sum[rt]; } PushDown(rt,r-l+1); ll res=0; int m=(l+r)>>1; if(L<=m) res+=query(L,R,lson); if(R>m) res+=query(L,R,rson); return res; } int main( ) { int n,m,a,b,c; scanf("%d%d",&n,&m); build(1,n,1); while(m--) { getchar(); char op[2]; scanf("%S",&op); if(op[0]=='C') { scanf("%d%d%d",&a,&b,&c); update(a,b,c,1,n,1); } else { scanf("%d%d",&a,&b); ll t; t=query(a,b,1,n,1); printf("%lld\n",t); } } }
(5)成段覆盖,单点最大小
飞翔
#includeusing namespace std ; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn = 200020; int Begin[maxn << 2], End[maxn << 2]; int be[maxn],en[maxn],w[maxn]; long long sum[maxn],d[maxn]; void pushdown(int rt)//向下跟新 { if(!Begin[rt<<1]) Begin[rt<<1]=Begin[rt]; if(!Begin[rt<<1|1]) Begin[rt<<1|1]=Begin[rt]; if(!End[rt]) return ; End[rt<<1]=End[rt<<1|1]=End[rt]; End[rt]=0;///优化用过了就可以不用了 } void build(int l , int r , int rt) { Begin[rt]=End[rt]=0; if(l==r) return ; int m = (l+r) >> 1 ; build(lson); build(rson); } void update(int L , int R , int k , int l , int r , int rt) { if(L<=l && r<=R) { if(!Begin[rt])///很简单的道理,我跟新过了就不跟新了; Begin[rt]=k; End[rt]=k; return ; } pushdown(rt); int m=(l+r) >> 1; if(m>=L) update(L,R,k,lson); if(m<R) update(L,R,k,rson); } void pushall(int l , int r , int rt) { if(l==r) { be[l]=Begin[rt],en[l]=End[rt]; return ; } pushdown(rt); int m=(l+r)>>1; pushall(lson); pushall(rson); } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { n--; build(1,n,1);///建树 for(int i=1 ; i<=n ; i++) scanf("%d",&w[i]); for(int i=1 ; i<=m ; i++) { int u,v; scanf("%d%d",&u,&v); if(u>v)//防止意外 swap(u,v); update(u,v-1,i,1,n,1);///u到v区间更新为i(天); } pushall(1,n,1);//找到每一段路的开始时间与结束时间 memset(d,0,sizeof(d)); for(int i=1 ; i<=n ; i++)//每一段路的开始费用与结束费用 { if(be[i]) { d[be[i]]+=w[i]; d[en[i]+1]-=w[i]; } } sum[0]=0; for(int i=1 ; i<=m ; i++)///类似与扫描线,一天一天的扫过去 { sum[i]=sum[i-1]+d[i]; printf("%lld\n",sum[i]); } } }
(6)
最大值(区间修改)
总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 65536kB
描述
在N(1<=n<=100000)个数A1…An组成的序列上进行M(1<=m<=100000)次操作,操作有两种:
(1)1 LR C:表示把A[L]到A[R]增加C(C的绝对值不超过10000);
(2)2 LR:询问A[L]到A[R]之间的最大值。
输入
第一行输入N(1<=N<=100000),表示序列的长度,接下来N行输入原始序列;接下来一行输入M(1<=M<=100000)表示操作的次数,接下来M行,每行为1 L R C或2 L R
输出
对于每个操作(2)输出对应的答案。
#include#include #include #include #include <string> #include #include #include #include #include #include #include
(7)区间修改的总模板
"code" class="cpp">#include#include <string.h> #include #include #include #include #define MAXN 100010 #define inf 0x3f3f3f3f using namespace std; struct node{ int l,r;//区间[l,r] int add;//区间的延时标记 int sum;//区间和 int mx; //区间最大值 int mn; //区间最小值 }tree[MAXN<<2];//一定要开到4倍多的空间 void pushup(int index){ tree[index].sum = tree[index<<1].sum+tree[index<<1|1].sum; tree[index].mx = max(tree[index<<1].mx,tree[index<<1|1].mx); tree[index].mn = min(tree[index<<1].mn,tree[index<<1|1].mn); } void pushdown(int index){ //说明该区间之前更新过 //要想更新该区间下面的子区间,就要把上次更新该区间的值向下更新 if(tree[index].add > 0){ //替换原来的值 /* tree[index<<1].sum = (tree[index<<1].r-tree[index<<1].l+1)*tree[index].add; tree[index<<1|1].sum = (tree[index<<1|1].r-tree[index<<1|1].l+1)*tree[index].add; tree[index<<1].mx = tree[index].add; tree[index<<1|1].mx = tree[index].add; tree[index<<1].mn = tree[index].add; tree[index<<1|1].mn = tree[index].add; tree[index<<1].add = tree[index].add; tree[index<<1|1].add = tree[index].add; tree[index].add = 0;*/ //在原来的值的基础上加上val tree[index<<1].sum += (tree[index<<1].r-tree[index<<1].l+1)*tree[index].add; tree[index<<1|1].sum +=(tree[index<<1|1].r-tree[index<<1|1].l+1)*tree[index].add; tree[index<<1].mx += tree[index].add; tree[index<<1|1].mx += tree[index].add; tree[index<<1].mn += tree[index].add; tree[index<<1|1].mn += tree[index].add; tree[index<<1].add += tree[index].add; tree[index<<1|1].add += tree[index].add; tree[index].add = 0; } } void build(int l,int r,int index){ tree[index].l = l; tree[index].r = r; tree[index].add = 0;//刚开始一定要清0 if(l == r){ scanf("%d",&tree[index].sum); tree[index].mn = tree[index].mx = tree[index].sum; return ; } int mid = (l+r)>>1; build(l,mid,index<<1); build(mid+1,r,index<<1|1); pushup(index); } void updata(int l,int r,int index,int val){ if(l <= tree[index].l && r >= tree[index].r){ /*把原来的值替换成val,因为该区间有tree[index].r-tree[index].l+1 个数,所以区间和 以及 最值为: */ /*tree[index].sum = (tree[index].r-tree[index].l+1)*val; tree[index].mn = val; tree[index].mx = val; tree[index].add = val;//延时标记*/ //在原来的值的基础上加上val,因为该区间有tree[index].r-tree[index].l+1 //个数,所以区间和 以及 最值为: tree[index].sum += (tree[index].r-tree[index].l+1)*val; tree[index].mn += val; tree[index].mx += val; tree[index].add += val;//延时标记 return ; } pushdown(index); int mid = (tree[index].l+tree[index].r)>>1; if(l <= mid){ updata(l,r,index<<1,val); } if(r > mid){ updata(l,r,index<<1|1,val); } pushup(index); } int query(int l,int r,int index){ if(l <= tree[index].l && r >= tree[index].r){ //return tree[index].sum; return tree[index].mx; //return tree[index].mn; } pushdown(index); int mid = (tree[index].l+tree[index].r)>>1; int ans = 0; int Max = 0; int Min = inf; if(l <= mid){ ans += query(l,r,index<<1); Max = max(query(l,r,index<<1),Max); Min = min(query(l,r,index<<1),Min); } if(r > mid){ ans += query(l,r,index<<1|1); Max = max(query(l,r,index<<1|1),Max); Min = min(query(l,r,index<<1|1),Min); } //return ans; return Max; //return Min; } int main() { int n,m,q,x,y,z; while(~scanf("%d%d",&n,&m)){ build(1,n,1); while(m--){ scanf("%d",&q); if(q == 1){ cout<<"查询:(x,y)"<<endl; scanf("%d %d",&x,&y); cout< 1)<<endl; } else{ cout<<"更新(x,y)为z:"<<endl; scanf("%d %d %d",&x,&y,&z); updata(x,y,1,z); for(int i = 1; i <= n; ++i){ printf("a[%d] = %d\n",i,query(i,i,1)); } } } } return 0; }
(8)区间修改区间整除
题意
初始a数组为0,给你一个全排列的b数组,q次询问add x y为a数组区间x y增加1,query x y查询a数组整除b数组对应下标的和
题解
区间操作很容易想到线段树
初始每个叶子节点赋值为b[i],维护一个区间最小值min,和区间和sum
对于每个add,区间[X,Y]最小值减1,如果当前区间最小值=1,就继续往下更新,如果更新到叶子节点并且min=1,sum+1
对于每个query,查询区间[X,Y]sum,如果区间min=0,再去暴力更新区间(可以知道一共q次询问,q/1+q/2+q/3+....q/n为调和级数,复杂度O(logn))
总复杂度O(nlog^2 n)
这个题一眼望过去,区间更新,区间求和,很明显就可以想到用线段树进行维护。
但是这个问题的难点在于这个式子 c[i]=ai/bi的求和相对来说不好直接处理。在这里,我们需要对这个式子进行分析。我们需要发现,对于每一个c[i],它的值倘若能够成功加1,那么必定是ai在区间更新中增加了bi 。因此我们就可以往这个方向去考虑。
但是对ai不断的加1并判断与bi的关系,处理起来相对比较麻烦,因此我们就可以逆向考虑:使得在每次更新中bi-1,而若bi减到0,则使答案+1,并使得bi重新变为原值。
如此一来,我们只需要用线段树对区间bi的最小值min以及答案c[i]进行维护即可
#include#define maxn 100005 using namespace std; typedef long long ll; struct Tree{ ll sum,valb,minn,add; }tr[maxn<<2]; int n,q; int a[maxn],b[maxn]; void push_up(int rt){//维护区间bi的最小值以及答案 tr[rt].minn=min(tr[rt<<1].minn,tr[rt<<1|1].minn); tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum; } void push_down(int rt){//lazy操作 if(tr[rt].add){ tr[rt<<1].add+=tr[rt].add; tr[rt<<1|1].add+=tr[rt].add; tr[rt<<1].minn-=tr[rt].add; tr[rt<<1|1].minn-=tr[rt].add; tr[rt].add=0; } } void build(int l,int r,int rt){//建树 tr[rt].add=tr[rt].sum=0; if(l==r){ tr[rt].minn=tr[rt].valb=b[l]; tr[rt].sum=0; return; } int mid=(l+r)>>1; build(l,mid,rt<<1); build(mid+1,r,rt<<1|1); push_up(rt); } void update(int L,int R,int l,int r,int rt){//区间更新 //如果询问区间[L,R]能被覆盖且[L,R]的最小值大于0,则进行-1操作 if(L<=l&&R>=r&&tr[rt].minn>1){ tr[rt].add++; tr[rt].minn--; return; } //否则不断遍历到叶子结点并判断当前bi的值是否为0 if(l==r&&tr[rt].minn==1){ tr[rt].sum++; tr[rt].minn=tr[rt].valb; tr[rt].add=0; return; } int mid=(l+r)>>1; push_down(rt); if(L<=mid) update(L,R,l,mid,rt<<1); if(R>mid) update(L,R,mid+1,r,rt<<1|1); push_up(rt); } ll query(int L,int R,int l,int r,int rt){//区间求和 if(L<=l&&R>=r){ return tr[rt].sum; } int mid=(l+r)>>1; push_down(rt); ll ans=0; if(L<=mid) ans+=query(L,R,l,mid,rt<<1); if(R>mid) ans+=query(L,R,mid+1,r,rt<<1|1); return ans; } int main() { while(~scanf("%d%d",&n,&q)){ for(int i=1;i<=n;i++){ scanf("%d",&b[i]); } build(1,n,1); while(q--){ string str; cin>>str; int l,r; scanf("%d%d",&l,&r); if(str[0]=='a'){ update(l,r,1,n,1); } else{ ll res=query(l,r,1,n,1); printf("%lld\n",res); } } } return 0; }
感激这位大牛的模板