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