一、线段树简介
作为菜鸟,考完试后又开始 跪题 ,今天刚刚学习了 比较水的线段树;
线段树其实就是一颗二叉树,然后节点包含区间等线段域, 然后根据不同要求来增加不同的域属性; 比如下面的这颗线段树
线段树的操作主要包括 建立 ,插入,删除等操作;
首先是线段树的结构:
struct Tree{
int ma,mi;// 根据需要使用不同的域;
int left,right;
};
然后是线段树的建立:
void build(int t,int l,int r) // qihzogn
{
cow[t].left=l;
cow[t].right=r;
if(l<=r-1)
{
int mid=(cow[t].left+cow[t].right)/2;
build(t*2,l,mid);
build(t*2+1,mid+1,r);
cow[t].ma=max(cow[t*2].ma,cow[t*2+1].ma);
cow[t].mi=min(cow[t*2].mi,cow[t*2+1].mi);
}
else
{
cow[t].ma=cow[t].mi=hei[l];
return ;
}
}
二、练习
然后是借用线段树解决了TOJ 上的三道比较简单题: 2762 Balanced Lineup 求一个区间的最值问题;3505. Naughty Mike : 求区间和问题, 2913. Frequent values 稍微复杂一点的线段树(Ps: 一般用线段树的数据量都不小,所以输入输出一般用scanf 和 printf 才不会TLE......)
1,TOJ2762 Balanced Lineup
题意比较简单就是求区间最值的,很基础的 线段树
代码如下:
#include <stdio.h>
#include <iostream>
#define INF 0x3f3f3f3f
using namespace std;
const int Max=50002;
struct Tree{
int ma,mi;
int left,right;
};
Tree cow[Max*4];
int hei[Max],ma,mi;
void build(int t,int l,int r) // qihzogn
{
cow[t].left=l;
cow[t].right=r;
if(l<=r-1)
{
int mid=(cow[t].left+cow[t].right)/2;
build(t*2,l,mid);
build(t*2+1,mid+1,r);
cow[t].ma=max(cow[t*2].ma,cow[t*2+1].ma);
cow[t].mi=min(cow[t*2].mi,cow[t*2+1].mi);
}
else
{
cow[t].ma=cow[t].mi=hei[l];
return ;
}
}
void query(int t,int a,int b)
{
if(a>b) return ;
if(cow[t].left==a&&cow[t].right==b)
{
if(ma<cow[t].ma) ma=cow[t].ma;
if(mi>cow[t].mi) mi=cow[t].mi;
return ;
}
else
{
int mid=(cow[t].left+cow[t].right)/2;
if(mid<a) query(t*2+1,a,b);
else if(mid>b) query(t*2,a,b);
else
{
query(t*2,a,mid);
query(t*2+1,mid+1,b);
}
}
}
int main()
{
int N,Q;
while(~scanf("%d%d",&N,&Q))
{
for(int i=1;i<=N;i++)
scanf("%d",&hei[i]);
build(1,1,N);
int a,b;
for(int i=0;i<Q;i++)
{
scanf("%d%d",&a,&b);
ma=0;mi=INF;
query(1,a,b);
printf("%d\n",ma-mi);
}
}
}
2,TOJ 3505. Naughty Mike
这个跟上一个大致一样,只不过这个是求区间的和的,涉及到的操作也是 建树 ,查询,更新
代码:
/*
Accepted 3505 C++ 2.0K 0'00.16"
*/
#include <string.h>
#include <stdio.h>
#define Max_Num 100005
int T,n,s[Max_Num],q,a,b;
char ch[10];
struct Tree{
int l,r;
long long sum;
};
Tree t[Max_Num*3];
void build_tree(int root ,int left,int right)
{
t[root].l=left;
t[root].r=right;
if(left<right)
{
int mid =(left+right)/2;
build_tree(root*2,left,mid);
build_tree(root*2+1,mid+1,right);
t[root].sum=t[root*2].sum+t[root*2+1].sum;
}
else if(left==right)
t[root].sum=s[left];
}
long long query(int root,int left,int right)
{
if(left>right) return 0;
if(t[root].l==left&&t[root].r==right)
{
return t[root].sum;
}
else
{
int mid= (t[root].l+t[root].r)/2;
if(left>mid)
{
return query(root*2+1,left,right);
}
else if(mid>right)
{
return query(root*2,left,right);
}
else
{
return query(root*2,left,mid)+query(root*2+1,mid+1,right);
}
}
}
void update(int root,int left,int right)
{
if(left>right) return ;
if(left<right)
{
int mid= (left+right)/2;
if(a>mid) update(root*2+1,mid+1,right);
else if(a<=mid) update(root*2,left,mid);
t[root].sum=t[root*2].sum+t[root*2+1].sum;
}
else t[root].sum=s[left];
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1; i<=n;i++)
scanf("%d",&s[i]);
build_tree(1,1,n);
scanf("%d",&q);
while(q--)
{
scanf("%s%d%d",ch,&a,&b);
if(strcmp(ch,"Inquire")==0)
{
printf("%d\n",query(1,a,b));
}
else if(strcmp(ch,"Add")==0)
{
s[a]+=b;
update(1,1,n);
}
else
{
s[a]-=b;
if(s[a]<0) s[a]=0;
update(1,1,n);
}
}
}
}
3,TOJ 2913. Frequent values
这个相对繁琐, 就是给你一串单调不减的数字 ,然后给你个区间,让你求这个区间内的连续相等的最长的数字出现的次数;比如: 11333 如果区间是 1,3 则应该输出2,因为.在区间内,连续的1 出现的最多;这个题就需要多考虑,从中间断开在合并的过程了,简单注释在代码里面; 写的不优化。。。据说还可以离散化,但是菜鸟还不会,,以后再学吧
/*
Accepted 2913 C++ 2.6K 0'00.44" 10616K
*/
#include <stdio.h>
#define Max_Num 100005
#define Max(a,b) (a>b?a:b)
struct Tree
{
int l,r;//节点边界
int ma,val;//区间的最多数量的数目和值
int lma,lval,rma,rval;//左右两端的最多数量的数目和值: 因为需要考虑合并是否产生最大值的问题,
//所以需要再加左右两端的域
};
Tree t[Max_Num*3];
int n,q,s[Max_Num],tmp,a,b,ans;
void build_tree(int root, int left,int right)
{//一般的建树过程
t[root].l=left;
t[root].r=right;
if(left<right)
{
int mid=(left+right)/2;
build_tree(root*2,left,mid);
build_tree(root*2+1,mid+1,right);
//以下的if..else 块求出没有考虑中间断开的情况
if(t[root*2].ma>t[2*root+1].ma)
{
t[root].ma=t[root*2].ma;
t[root].val=t[root*2].val;
}
else
{
t[root].ma=t[root*2+1].ma;
t[root].val=t[root*2+1].val;
}
t[root].lma =t[root*2].lma;
t[root].rma =t[root*2+1].rma;
t[root].lval=t[root*2].lval;
t[root].rval=t[root*2+1].rval;
//当左子树的右端最长的数等于右子树的左端最长的数,表示可以合并
if(t[root*2].rval==t[root*2+1].lval)
{
tmp=t[root*2].rma+t[root*2+1].lma;
if(tmp>t[root].ma)
{//是否需要更新当前节点的ma;
t[root].ma=tmp;
t[root].val=t[root*2].rval;
}
if(t[root*2].lval==t[root*2+1].lval)//考虑到如果左子树的数都相同的话还要更新下 t[root].lma
t[root].lma+=t[root*2+1].lma;
if(t[root*2].rval==t[root*2+1].rval)//考虑到如果右子树的数都相同的话还要更新下 t[root].rma
t[root].rma+=t[root*2].rma;
}
}
else
{
t[root].lma=t[root].rma=t[root].ma=1;
t[root].lval=t[root].rval=t[root].val=s[left];
}
}
void query(int root ,int left,int right)
{
if(left>right) return ;
if(t[root].l==left&&t[root].r==right)
{
if(ans<t[root].ma) ans=t[root].ma;
}
else
{
int mid=(t[root].l+t[root].r)/2;
if(mid>=right) query(root*2,left,right);
else if(mid<left) query(root*2+1,left,right);
else
{
query(root*2,left,mid);
query(root*2+1,mid+1,right);
//下面是左子树的右端最长的数等于右子树的左端最长的数的情况的合并
int s1=0,s2=0;
if(t[root*2].rval==t[root*2+1].lval)
{
if(s[left]==t[root*2].rval) s1=mid-left+1;//跟建树的一样, 单独考虑子树是不是全相等的情况
else s1=t[root*2].rma;
if(s[right]==t[root*2+1].lval) s2=right-mid;
else s2=t[root*2+1].lma;
if(ans<s1+s2) ans=s1+s2;
}
}
}
}
int main()
{
while(scanf("%d",&n))
{
if(n==0) break;
scanf("%d",&q);
for(int i=1; i<=n;i++)
scanf("%d",&s[i]);
build_tree(1,1,n);
while(q--)
{
scanf("%d%d",&a,&b);
ans=0;
query(1,a,b);
printf("%d\n",ans);
}
}
}