功能
线段树是线段组成的(二叉)树,可以解决许多区间问题。因为他的灵活性和较高的效率(二叉树,效率为log2级别的),甚至常常可以取代树状数组和RMQ等区间算法(树状数组只能向上修改,向下取值,RMQ无法即时修正,而线段树这两个问题都可以解决)。需要注意的是线段分两种:不重叠和重叠(点和真正的线段)。下面以不重叠为例。
1.建树
首先tr[1].L肯定就是左端点,tr[1].R是右端点,然后就需要推左子树2和右子树3(和堆的编号规则一样)。那么左子树就是L~mid,右子树就是mid+1~R。下面给出代码:
void Build(int id,int L,int R)
{
int mid=L+(R-L>>1); //取中
tr[id].L=L;tr[id].R=R;
if (L<=mid&&mid+1<=R) Build(id*2,L,mid),Build(id*2+1,mid+1,R);
//递归造出左子树和右子树
}
2.修正
修正也是利用了分治的想法,对于加进来的线段L~R,我们需要通过分割把这个线段覆盖到建造好的线段树中去。先取当前线段树节点的mid,然后来进行分治:
1.R<=mid,那么就在右子树,到右子树中去处理。
2.L>mid,那么就在左子树,到左子树中去处理。
3.上面两个都不满足,那么L~R就被分割了,左子树和右子树都要处理。
下面给出代码:
void Insert(int id,int L,int R) //在id节点中加入L~R
{
if (tr[id].L==L&&tr[id].R==R) //覆盖了这个节点
{
处理; //不同题目不同处理
return;
}
int mid=tr[id].L+(tr[id].R-tr[id].L>>1); //中点
if (R<=mid) Insert(id*2,L,R,x); else //左子树
if (L>mid) Insert(id*2+1,L,R,x); else //右子树
{
Insert(id*2,L,mid,x);
Insert(id*2+1,mid+1,R,x); //左右子树
}
}
3.询问
和修正基本一样,就是分割的思想。
int Ask(int id,int L,int R) //在id这个节点
{
if (tr[id].L==L&&tr[id].R==R) return 值; //不同题目不一样
int mid=tr[id].L+(tr[id].R-tr[id].L>>1);
if (R<=mid) return Ask(id*2,L,R); else //左子树
if (L>mid) return Ask(id*2+1,L,R); else //右子树
return 操作(Ask(id*2,L,mid),Ask(id*2+1,mid+1,R)); //不同题目不一样
//左右子树
}
4.区间覆盖问题
给你一堆线段,问你L~R被覆盖了多少,这个就是典型的重叠线段树。详见例子中的“区间覆盖问题”。
5.区间极值问题
给你一堆带权值的线段,最后问你区间极值,那么最典型的就是RMQ问题了,但是RMQ只是点权而已,真正插入线段的话线段树是非常不错的选择。详见例子中的“区间极值问题”。
ps:内含lazy思想。
6.动态统计问题
区间覆盖问题的拓展。
7.注意事项
首先有个问题就是线段树数组要开多大?按照拆分的规则2n就足够了,但是有可能就是最后有个冒尖的在下面,导致编号超出2n。所以我们要开4n。其次线段树的代码复杂度较高,且尽管是log2级别的但常数较大,所以能不用线段树尽量不要用。
模板(区间覆盖问题)
#include<cstdio>
const int maxn=100005,maxl=100005;
int n,L,R;
struct zzk
{
int L,R; //L和R表示这个节点的范围
bool cover; //cover表示这个节点有没有被覆盖
}tr[4*maxl];
void Build(int id,int L,int R) //建造id节点
{
int mid=L+(R-L>>1);
tr[id].L=L;tr[id].R=R;tr[id].cover=false;
if (L<mid&&mid<R) Build(id*2,L,mid),Build(id*2+1,mid,R);
}
void Pushdown(int id) //传递,如果id节点被覆盖了,那么左子树和右子树就都覆盖了
{
if (tr[id].R-tr[id].L==1||!tr[id].cover) return;
tr[id*2].cover=true;tr[id*2+1].cover=true;
}
void Insert(int id,int L,int R) //在id节点插入L~R这条线段
{
Pushdown(id); //传递下去,这里加不加其实都一样
if (tr[id].cover) return;
if (tr[id].L==L&&tr[id].R==R) //刚好重合
{
tr[id].cover=true;
return;
}
int mid=tr[id].L+(tr[id].R-tr[id].L>>1); //中点
if (R<=mid) Insert(id*2,L,R); else //左子树
if (L>=mid) Insert(id*2+1,L,R); else //右子树
{
Insert(id*2,L,mid);
Insert(id*2+1,mid,R); //左右子树
}
}
int Ask(int id,int L,int R)
{
Pushdown(id); //传递下去,不然就无法查找了
if (tr[id].L==L&&tr[id].R==R&&tr[id].cover) return R-L; //刚好重合且这一段被覆盖了
if (tr[id].R-tr[id].L==1) return 0; //到头了
int mid=tr[id].L+(tr[id].R-tr[id].L>>1);
if (R<=mid) return Ask(id*2,L,R); else
if (L>=mid) return Ask(id*2+1,L,R); else
return Ask(id*2,L,mid)+Ask(id*2+1,mid,R);
}
char getrch() {char ch=getchar();while (ch!='A'&&ch!='S') ch=getchar();return ch;}
int main()
{
int i,x,y;
char ch;
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
scanf("%d%d%d",&n,&L,&R);
Build(1,L,R); //建造一棵L~R的树
for (i=1;i<=n;i++)
{
ch=getrch();scanf("%d%d",&x,&y);
if (ch=='A') Insert(1,x,y);//插入x~y的线段
if (ch=='S') printf("%d\n",Ask(1,x,y)); //询问x~y的覆盖个数
}
return 0;
}