线段树学习记录

学完了自己半残不残的Tarjan算法,为于机房同步,我开始学习线段树。。。。。。
先给出线段树定义:
线段树是一种 二叉搜索树 ,与 区间树 相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的 空间复杂度 为2N,因此有时需要离散化让空间压缩。(摘自百度百科)
先介绍一下我的线段树写法
struct hp{
     int value;//每一个树节点信息。
}node[4*maxn];//maxn是数点个数。
int a[maxn];//原数列信息。
之所以不保存了lson,rson的节点位置,是因为我采用完全二叉树的建法,即i的左儿子为2*i,右儿子为2*i+1;而不保存了l,r是因为可以在传递参数时解决。 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.以单点更新,求区间最小值为例
自下而上更新:
void updata(int i)
{
node[i].value=min(node[i*2].value,node[i*2+1].value);//代码核心,不同程序基本只有此处不同。
}
建树:
 void build(int i,int l,int r)//建立区间为[l,r](注意为闭区间)
{
if (l==r)//已经找到叶子节点
 {
   node[i].value=a[l];
   return;
 }
build(i*2,l,(l+r)/2);//建立左子树(注意区间范围)
build(i*2+1,(l+r)/2+1,r);//建立右子树(注意区间范围)
updata(i);//更新节点信息,注意先查找后更新。
}
单点更新:
void insert(int i,int l,int r,int x,int y)
{
int mid;
if ((r==l)&&(l==x))//已查询到此节点
 {
    node[i].value+=y;//更新
   return;
 }
mid=(l+r)/2;
if (x<=mid)
 insert(i*2,l,mid,x,y);//在左子树
else
 insert(i*2+1,mid+1,r,x,y);//在右子树
updata(i);//更新
}
附本蒟蒻闪烁的繁星(Vijos国庆节模拟赛之繁星春水,P1881)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
using namespace std;
struct hp{
	int l,r,li,ri,ll,rl,maxl;
}node[800001];
int a[200001],f[200001],n,m;
void build(int i,int l,int r)
{
	node[i].l=l; node[i].r=r;
	node[i].rl=node[i].ll=node[i].maxl=1;
	node[i].ri=node[i].li=0;
	if (l==r)
	  {
	    f[l]=i;
	    return;
	  }
	build(i*2,l,(l+r)/2);
	build(i*2+1,(l+r)/2+1,r);
}
void updata(int i)
{
	int fi,rs,ls,mid;
	if (i==1)
	  return;
	fi=i/2;  rs=fi*2+1; ls=fi*2;
	mid=(node[fi].r+node[fi].l)/2;
	node[fi].li=node[ls].li;
	node[fi].ll=node[ls].ll;
	node[fi].ri=node[rs].ri; 
	node[fi].rl=node[rs].rl;
	node[fi].maxl=max(node[i].maxl,max(node[ls].maxl,node[rs].maxl));
	if (node[ls].ri!=node[rs].li)
	  {
	    node[fi].maxl=max(node[fi].maxl,node[ls].rl+node[rs].ll);
	    if (node[ls].maxl==mid-node[fi].l+1)
	      node[fi].ll=node[ls].maxl+node[rs].ll;
	    if (node[rs].maxl==node[fi].r-mid)
	      node[fi].rl=node[rs].maxl+node[ls].rl;
	  }
	updata(fi);
}
int main()
{
	int i,x,n,m;
	scanf("%d",&n);
	build(1,1,n);
	scanf("%d",&m);
	for (i=1;i<=m;++i)
	  {
	    scanf("%d",&x);
		node[f[x]].ri=node[f[x]].li=1-node[f[x]].li;	
		updata(f[x]);
		printf("%d\n",node[1].maxl);
	  }

}
2.对于 单点更新 线段树中第几个非空叶节点(如joseph问题,POJ2828 Buy tickets等) ,我们可以用node[i].value来记录该区间有几个非空节点,查询时比较x与node[i*2].value即可。
 
           
void insert(int i,int l,int r,int x)
{
if (l==r)
 {
   node[i].value=0;//修改
   ans=l;
   return;
 }
if (x<=node[i*2])//左子树中
 insert(i*2,l,mid,x);
else//右子树中,注意减去左子树中数目。
 insert(i*2+1,mid+1,r,x-node[i*2]);
updata(i);
} 
处理[x,y]询问(PS:由于x,y不发生改变,亦可用全局变量)
 void query(int i,int l,int r,int x,int y)//在节点i的[l,r]区间内查询[x,y]
{
int mid;
if ((x<=l)&&(y>=r))//如果区间包含于其中,查询即可
 {
   ans=min(ans,node[i].value);
   return;
 }
mid=(l+r)/2;
if (x<=mid)//左子树有交集
 query(i*2,l,mid,x,y);
if (y>mid)//右子树有交集
 query(i*2+1,mid+1,r,x,y);
} 
附本蒟蒻约瑟夫问题(CODEVS1282)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int node[120001];
int n,m,ans,step;
void updata(int i)
{
	node[i]=node[i*2]+node[i*2+1];
}
void build(int i,int l,int r)
{
	int mid; mid=(l+r)/2;
	if (l==r)
	  {
	    node[i]=1;
	    return;
	  }
    build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	updata(i);
}
void insert(int i,int l,int r,int x)
{
	int mid; mid=(l+r)/2;
	if (l==r)
	  {
	    node[i]=0;
	    ans=l;   
	    return;
	  }
	if (x<=node[i*2])
	  insert(i*2,l,mid,x);
	else
	  insert(i*2+1,mid+1,r,x-node[i*2]);
	 updata(i); 
} 
void query(int i,int l,int r,int x,int y)
{
	int mid; mid=(l+r)/2;
	if ((x<=l)&&(y>=r))
	  {
	    ans+=node[i];
        return;
	  }
	if (x<=mid)
	  query(i*2,l,mid,x,y);
	if (y>mid)
	  query(i*2+1,mid+1,r,x,y);
}
int main()
{
	int i,k,now,x;
	scanf("%d%d",&n,&m);
	build(1,1,n); now=0;
	for (i=1;i<=n;++i)
	  {
	    ans=0; step=0;
	    now=now%n; x=0;
	    if (now!=0)
	      {
	        query(1,1,n,1,now);
	        x=ans; ans=0;
	  	  }
	    ans=node[1]-x;
	    k=m; 
	    if (ans>=k)
	      k+=x;
	    else
	      k-=ans;
	    if (k%node[1]!=0)
	      k=k%node[1];
	    else
	      k=node[1];
	    ans=0;
	    insert(1,1,n,k);
	    printf("%d ",ans);
	    now=ans;
	  }
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
对于区间修改(给区间整体加减乘实数除一个定值),区间查询类问题,我们可以对每一个节点设置一个delta,记录更新值,而不进行实质性更新,每当查询或询问到此节点时,在对delta进行下放,下放至左右子树,这样就保证了程序的效率;
建树,自下而上更新代码相同
1.区间修改,区间最值:
释放标记
void paint(int i,int  a)
{
     node[i].value+=a;
     delta[i]+=a;
}
标记下放
void pushdown(int i)
{
     paint(i*2,delta[i]);//左子树
     paint(i*2+1,delta[i]);//右子树
     delta[i]=0;//释放delta
}
区间更新[x,y]
void insert(int i,int l,int r,int x,int y,int a)
{
   int mid; mid=(l+r)/2;
   if (x<=l&&y>=r)
     {
        paint(i,a);//修改该区间的value,记录delta;
        return;
     }
   pushdown(i);//标记下放。
   if (x<=mid)
     insert(i*2,l,mid,x,y,a);
   if (y>mid)
     insert(i*2+1,mid+1,r,x,y,a);
   updata(i);
}
处理[x,y]询问(PS:由于x,y不发生改变,亦可用全局变量)
  void query(int i,int l,int r,int x,int y)//在节点i的[l,r]区间内查询[x,y]
{
int mid;
if ((x<=l)&&(y>=r))//如果区间包含于其中,查询即可
 {
   ans=min(ans,node[i].value);
   return;
 }
 pushdown(i); //标记下放
mid=(l+r)/2;
if (x<=mid)//左子树有交集
 query(i*2,l,mid,x,y);
if (y>mid)//右子树有交集
 query(i*2+1,mid+1,r,x,y);
} 
2.值得一提的是,当区间最值改为区间求和时,node[i]应加上a*区间长度,所以paint和pushdown应多传递l和r两变量,对value值进行修改时 node[i].value+=a;改为node[i].value+=a*(r-l+1);value值不变
附本蒟蒻线段树练习三(CODEVS1082)
#include<iostream> 
#include<cstring>
#include<cstdio>
using namespace std;
struct hp{
	long long value;
}node[800001];
int n,m;
long long ans;
long long a[200001]={0},delta[2000001]={0};
void updata(int i)
{
	node[i].value=node[i*2].value+node[i*2+1].value;
}
void paint(int i,long long a,int l,int r)
{
	node[i].value+=a*(r-l+1);
	delta[i]+=a;
}
void pushdown(int i,int l,int r)
{
	int mid; mid=(l+r)/2;
	paint(i*2,delta[i],l,mid);
	paint(i*2+1,delta[i],mid+1,r);
	delta[i]=0;
}
void build(int i,int l,int r)
{
	if (l==r)
	  {
	    node[i].value=a[l];
	    return;
	  }
	build(i*2,l,(l+r)/2);
	build(i*2+1,(l+r)/2+1,r);
	updata(i);
}
void query(int i,int l,int r,int x,int y)
{
	int mid;
	if ((x<=l)&&(y>=r))
	  {
	    ans+=node[i].value;
	    return;
	  } 
	if  (delta[i]!=0)
	pushdown(i,l,r);
	mid=(l+r)/2;
	if (x<=mid) query(i*2,l,mid,x,y);
	if (y>mid) query(i*2+1,mid+1,r,x,y);
}
void insert(int i,int l,int r,int x,int y,int z)
{
	int mid;
	if ((x<=l)&&(y>=r))
	  {
	    paint(i,z,l,r);
	    return;
	  }
	pushdown(i,l,r); mid=(l+r)/2;
	if (x<=mid)
	  insert(i*2,l,mid,x,y,z);
	if (y>mid)
	  insert(i*2+1,mid+1,r,x,y,z);
	updata(i);
}
int main()
{
	int i,kind,j,x,y;
	long long z;
	memset(delta,0,sizeof(delta));
	scanf("%d",&n);
	for (i=1;i<=n;++i)
	  scanf("%d",&a[i]);
	build(1,1,n);
	scanf("%d",&m);
	for (i=1;i<=m;++i)
	  {
	    scanf("%d",&kind);
		if (kind==1)
		  {
		    scanf("%d%d%lld",&x,&y,&z);
		    insert(1,1,n,x,y,z);
		  }  
		if (kind==2)
		  {
		  	scanf("%d",&x,&y);
		    ans=0;
		    query(1,1,n,x,y);
		    cout<<ans<<endl;
		  }
	  } 
}
 3.对于给区间中的每一个值开平方抑或乘方等(即更新值不同),只能立即对标记下放至叶节点,但必须对更新条件加以判断,否则TLE
附本蒟蒻上帝造题的七分钟2(CODEVS2492)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define mid (l+r)/2
#define lch i<<1,l,mid
#define rch i<<1|1,mid+1,r
using namespace std;
long long node[400001],a[100001],ans;
bool flag[400001]={false};
int n,m;
void updata(int i)
{
	node[i]=node[i<<1]+node[i<<1|1];
	flag[i]=flag[i<<1]&&flag[i<<1|1];
}
void build(int i,int l,int r)
{
	if (l==r)
	  {
	    node[i]=a[l];
	    flag[i]=(node[i]<=1);//0或1不需要更新
	    return;
	  }
	build(lch); build(rch);
	updata(i);
}
void paint(int i,int l,int r)
{
	if (flag[i]) return;//如果左右子树均不必要更新,return
	if (l==r)
	  {
	    node[i]=floor(sqrt(node[i]));
	    flag[i]=(node[i]<=1);//0或1不需要更新
            return;
	  }
	paint(lch); paint(rch);
	updata(i);
}
void insert(int i,int l,int r,int x,int y)
{
	if (x<=l&&y>=r)
	  {
	    paint(i,l,r);
	    return;
	  }
	if (x<=mid) insert(lch,x,y);
	if (y>mid) insert(rch,x,y);
	updata(i);
}
void query(int i,int l,int r,int x,int y)
{
	if (x<=l&&y>=r)
	  {
	    ans+=node[i];
	    return;
	  }
	if (x<=mid) query(lch,x,y);
	if (y>mid) query(rch,x,y);
}
int main()
{
	int i,x,y,kind;
	scanf("%d",&n);
	for (i=1;i<=n;++i)
	  {
	    a[i]=0; scanf("%lld",&a[i]);
	  }
	build(1,1,n);
    scanf("%d",&m);
    for (i=1;i<=m;++i)
      {
        scanf("%d%d%d",&kind,&x,&y);
        if (x>y) swap(x,y);
        if (kind==1)
          {
          	ans=0;
            query(1,1,n,x,y);
            printf("%lld\n",ans);
          }
        if (kind==0)
          insert(1,1,n,x,y);
      }
}
4.对于每个点有限制状态(如最小到0)的区间修改。。。。。。乱搞吧
附本蒟蒻数轴染色(CODEVS1191)
#include<iostream>
#include<cstdio>
#include<cstring>
#define mid (l+r)/2 
using namespace std;
int node[800001],delta[800001];
int n,m;
void updata(int i)
{
	node[i]=node[i*2]+node[i*2+1];
}
void build(int i,int l,int r)
{
	if (l==r)
	  {
	    node[i]=1;
	    return;
	  }
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	updata(i);
}
void paint(int i,int l,int r,int a)
{
	int t;
	t=node[i]-a*(r-l+1);
	node[i]=max(0,t);
	delta[i]+=a;
} 
void pushdown(int i,int l,int r)
{
	paint(i*2,l,mid,delta[i]);
	paint(i*2+1,mid+1,r,delta[i]);
	delta[i]=0;
}
void insert(int i,int l,int r,int x,int y)
{
	if ((x<=l)&&(y>=r))
	  {
	    paint(i,l,r,1);
	    return;
	  }
	pushdown(i,l,r);
	if (x<=mid) insert(i*2,l,mid,x,y);
	if (y>mid) insert(i*2+1,mid+1,r,x,y);
	updata(i);
}
int main()
{
	int i,x,y;
	scanf("%d%d",&n,&m);
	build(1,1,n);
	for (i=1;i<=m;++i)
	  {
	    scanf("%d%d",&x,&y);
		insert(1,1,n,x,y);
		printf("%d\n",node[1]);  
	  }
	return 0;
}
5.对于每个点只有两种状态的线段树,可以用delta[i]记录修改次数,当delta[i]为奇数时,再下放delta
附本蒟蒻开关灯(CODEVS1690)
#include<iostream>
#include<cstdio>
#include<cstring>
#define mid (l+r)/2
using namespace std;
int node[400001],delta[400001];
int n,m,ans;
void updata(int i)
{
	node[i]=node[i<<1]+node[i<<1|1];
}
void paint(int i,int l,int r)
{
	node[i]=(r-l+1)-node[i];
	delta[i]+=1;
}
void pushdown(int i,int l,int r)
{
	if (delta[i]%2!=0)
	  {paint(i<<1,l,mid); paint(i<<1|1,mid+1,r);}
	delta[i]=0;
}
void insert(int i,int l,int r,int x,int y)
{
	if ((x<=l)&&(y>=r))
	  {
	    paint(i,l,r);
	    return;
	  }
	pushdown(i,l,r);
	if (x<=mid)
	  insert(i<<1,l,mid,x,y);
	if (y>mid)
	  insert(i<<1|1,mid+1,r,x,y);
	updata(i);
}
void query(int i,int l,int r,int x,int y)
{
	if ((x<=l)&&(y>=r))
	  {
	    ans+=node[i];
	    return;
	  }
	pushdown(i,l,r);
	if (x<=mid)
	  query(i<<1,l,mid,x,y);
	if (y>mid)
	  query(i<<1|1,mid+1,r,x,y);
}
int main()
{
	int i,kind,x,y;
	scanf("%d%d",&n,&m);
	for (i=1;i<=m;++i)
	  {
	    scanf("%d%d%d",&kind,&x,&y);
        if (kind==0)
          insert(1,1,n,x,y);
        if (kind==1)
          {
		    ans=0;
		    query(1,1,n,x,y);
		    printf("%d\n",ans);
		  }
	  }
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 感谢同机房神犇TA,rivebdell,yangfangyuan
                                                                                                                                                                                         lcomyn
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值