概念:
在一类问题中,我们需要经常处理可以映射在一个坐标轴上的一些固定线段,例如说映射在OX轴上的线段。由于线段是可以互相覆盖的,有时需要动态地取线段的并,例如取得并区间的总长度,或者并区间的个数等等。一个线段是对应于一个区间的,因此线段树也可以叫做区间树。
线段树常用于区间多次插入查询,经常改变数据。
而线段树的核心在于如何设计一个节点的信息
这里针对线段树的应用有三个方面:
1. 每次查询一个区间的最大差值(结点存放这个区间的最大最小值 3274)
2.不断修改区间内的数值,并查询区间的和
这里涉及到了修改,如果每次一修改就递归修改到叶子,效率非常低。所以在结点中设计一个inc,即加量。这个区间每个元素都应该添加的。只有当查询到了这个区间的时候才会递归修改下部的数值。
3.线段离散化。
离散化就是压缩区间,使原有的长区间映射到新的短区间,但是区间压缩前后的覆盖关系不变。举个例子:
有一条1到10的数轴(长度为9),给定4个区间[2,4] [3,6] [8,10] [6,9],覆盖关系就是后者覆盖前者,每个区间染色依次为 1 2 3 4。
现在我们抽取这4个区间的8个端点,2 4 3 6 8 10 6 9
然后删除相同的端点,这里相同的端点为6,则剩下2 4 3 6 8 10 9
对其升序排序,得2 3 4 6 8 9 10
然后建立映射
2 3 4 6 8 9 10
↓ ↓ ↓ ↓ ↓ ↓ ↓
1 2 3 4 5 6 7
那么新的4个区间为 [1,3] [2,4] [5,7] [4,6],覆盖关系没有被改变。新数轴为1到7,即原数轴的长度从9压缩到6,显然构造[1,7]的线段树比构造[1,10]的线段树更省空间,搜索也更快,但是求解的结果却是一致的
离散化时有一点必须要注意的,就是必须先剔除相同端点后再排序,这样可以减少参与排序元素的个数,节省时间。
数据存储:
链式或者数组形式,因为是类完全二叉树所以可以用数组存储,且方便定位。假设区间元素有N,则总结点个数为2^(logN+1),实测发现一般4*N就够了
POJ 3274
/* 核心函数 1.buildtree :递归生成,自下向上建立。即利用下层数据。孩子结点为loc<<1,loc<<1|1 2.query:这个函数还是很有技巧性,每次查询先分区间类型(包含,无交集,部分涵盖) 无交集直接pass,包含则直接提取信息。部分涵盖则判断与Mid的关系。 看是否可以完全转到另一个分支中去 3.巧用位运算,加速程序。左右孩子分别为Loc<<1,loc<<1|1 */ #include <stdio.h> #include <string.h> #define INF 10000000 #define maxn 200005 #define max(a,b) (a)>(b)?(a):(b) #define min(a,b) (a)<(b)?(a):(b) struct node { int l,r; int mn,mx; }nodes[3*maxn]; int a[maxn]; int qmin,qmax; void buildtree(int loc,int l,int r) { int mid; nodes[loc].l=l;nodes[loc].r=r; if(l==r) nodes[loc].mn=nodes[loc].mx=a[l]; else { mid=(l+r)>>1; buildtree(loc<<1,l,mid); buildtree(loc<<1|1,mid+1,r); nodes[loc].mn=min(nodes[loc<<1].mn,nodes[loc<<1|1].mn); nodes[loc].mx=max(nodes[loc<<1].mx,nodes[loc<<1|1].mx); } } void query(int loc,int l,int r) { if(nodes[loc].mn>=qmin && nodes[loc].mx<=qmax) return; if(nodes[loc].l==l && nodes[loc].r==r) { qmin=min(nodes[loc].mn,qmin); qmax=max(nodes[loc].mx,qmax); } else { int mid; mid=(nodes[loc].l+nodes[loc].r)>>1; if(r<=mid) query(loc<<1,l,r); else if(l>mid) query(loc<<1|1,l,r); else query(loc<<1,l,mid),query(loc<<1|1,mid+1,r); } } int main() { int N,C; int e,r; while(scanf("%d%d",&N,&C)!=EOF) { int i; for(i=1;i<=N;i++) scanf("%d",a+i); buildtree(1,1,N); for(i=0;i<C;i++) { scanf("%d%d",&e,&r); qmax=-INF;qmin=INF; query(1,e,r); printf("%d\n",qmax-qmin); } } return 0; }
POJ 3468
延迟更新
/* Add函数每次添加都只添加到该区间,并不递归全部更新(延缓更新) 在查询的过程中才会更新 */ #include <stdio.h> #include <string.h> #include <iostream> #define INF 10000000 #define maxn 100000 #define max(a,b) (a)>(b)?(a):(b) #define min(a,b) (a)<(b)?(a):(b) using namespace std; struct node { int l,r; __int64 sum; __int64 inc; }nodes[3*maxn];//结点设计;左右区间,待增加量,和值 int num[maxn]; int qmin,qmax; void add(int i,int a,int b,__int64 c) { if(nodes[i].l==a&&nodes[i].r==b) { nodes[i].inc+=c; return; } nodes[i].sum+=c*(b-a+1);//运行到这里,表明这个是大区间,包含添加的区间所以sum是他们的个数之和 int mid=(nodes[i].l+nodes[i].r)>>1; if(b<=mid) add(i<<1,a,b,c); else if(a>mid) add(i<<1|1,a,b,c); else { add(i<<1,a,mid,c); add(i<<1|1,mid+1,b,c); } } __int64 buildtree(int loc,int l,int r) { int mid; nodes[loc].l=l;nodes[loc].r=r; nodes[loc].inc=0; if(l==r) return nodes[loc].sum=num[l]; else { mid=(l+r)>>1; nodes[loc].sum=buildtree(loc<<1,l,mid)+buildtree(loc<<1|1,mid+1,r); } return nodes[loc].sum; } __int64 query(int loc,int l,int r) { __int64 ans=0; if(nodes[loc].l==l && nodes[loc].r==r) { return nodes[loc].sum+(r-l+1)*nodes[loc].inc; } else { nodes[loc].sum+=(nodes[loc].r-nodes[loc].l+1)*nodes[loc].inc; int mid; mid=(nodes[loc].l+nodes[loc].r)>>1; add(loc<<1,nodes[loc].l,mid,nodes[loc].inc);//每次都只更新需要的区间,真妙~! add(loc<<1|1,mid+1,nodes[loc].r,nodes[loc].inc); nodes[loc].inc=0; if(r<=mid) ans=query(loc<<1,l,r); else if(l>mid) ans=query(loc<<1|1,l,r); else ans=query(loc<<1,l,mid)+query(loc<<1|1,mid+1,r); } return ans; } int main() { int n,q; int i; int a,b,c; char ch; while(scanf("%d%d",&n,&q)!=EOF) { for(i=1;i<=n;i++) scanf("%d",&num[i]); buildtree(1,1,n); for(i=1;i<=q;i++) { cin>>ch; if(ch=='C') { scanf("%d%d%d",&a,&b,&c); add(1,a,b,c); } else { scanf("%d%d",&a,&b); printf("%I64d\n",query(1,a,b)); } } } return 0; }
POj 2528
离散化,剪枝
/* 数据离散化经典使用 这里注意离散化的时候不能因为怕浪费空间就用char,会溢出 离散化映射使用数组内容->数组下标 */ #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #define MM 10000000+200 #define maxn 20010 #define max(a,b) (a)>(b)?(a):(b) #define min(a,b) (a)<(b)?(a):(b) using namespace std; struct node { int l,r; int color; }nodes[4*maxn]; int num[maxn]; int qmin,qmax; int cnt; int ans; int a[maxn],b[maxn]; int vis[MM]; void insert(int l,int r,int color,int loc)//从区间关系入手 { if(nodes[loc].color==color) return;//颜色一致,不用更新 if(r<nodes[loc].l || l>nodes[loc].r) return;//完全无覆盖 if(nodes[loc].l>=l && nodes[loc].r<=r) { nodes[loc].color=color;//已经内含 return; } //部分覆盖 if(nodes[loc].color>=0) { nodes[loc<<1].color=nodes[loc<<1|1].color=nodes[loc].color; nodes[loc].color=-1; } //此节点为多色,先更新孩子的颜色,再为-1; insert(l,r,color,loc<<1); insert(l,r,color,loc<<1|1); /*int mid=(nodes[loc].l+nodes[loc].r)>>1; if(r<=mid) insert(l,r,color,loc<<1); else if(l>mid) insert(l,r,color,loc<<1|1); else { insert(l,mid,color,loc<<1); insert(mid+1,r,color,loc<<1|1); }*/ } void buildtree(int loc,int l,int r) { int mid; nodes[loc].l=l;nodes[loc].r=r; nodes[loc].color=0; if(l==r) return ; else { mid=(l+r)>>1; buildtree(loc<<1,l,mid); buildtree(loc<<1|1,mid+1,r); } } void dfs(int loc) { if(nodes[loc].color==0) return; else if(nodes[loc].color>0 && vis[nodes[loc].color]==0) ans++,vis[nodes[loc].color]=1; else if(nodes[loc].color==-1) { dfs(loc<<1); dfs(loc<<1|1); } } int main() { int n,q,i; #ifndef ONLINE_JUDGE//提交的时候把这段删掉 freopen("input.txt","r",stdin); freopen("output.txt","w",stdout); #endif scanf("%d",&n); while(n--) { scanf("%d",&q); cnt=0;ans=0; memset(vis,0,sizeof(vis)); for( i=0;i<q;i++) { scanf("%d%d",a+i,b+i); if(vis[a[i]]==0) vis[a[i]]=1,num[cnt++]=a[i]; if(vis[b[i]]==0) vis[b[i]]=1,num[cnt++]=b[i]; } sort(num,num+cnt); int hash=0; for( i=0;i<cnt;i++) vis[num[i]]=++hash;//hash映射, buildtree(1,1,hash); for(i=0;i<q;i++) insert(vis[a[i]],vis[b[i]],i+1,1); memset(vis,0,sizeof(vis)); dfs(1); printf("%d\n",ans); } return 0; }