概念:
在一类问题中,我们需要经常处理可以映射在一个坐标轴上的一些固定线段,例如说映射在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;
}