学完了自己半残不残的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
|