概括:线段树是一种数据结构,针对需要动态 修改并且获取数据中一段信息(子段和,子段最小值等)的数据来使用
大体思路:使用一颗完全二叉树(数组实现)记录数据。树的叶子节点记录每一个数据元素,非叶子节点记录段信息。
建树,查询,修改都由二叉树的性质:第i个节点的左右孩子为2*i+1,2*i+2 递归的执行
建树思路:从根向下递归找叶子->找到叶子后对叶子赋值->赋值完叶子后回溯赋段信息
查找(查询某一段的信息)思路:从根向下递归点的段信息:
对于和查找的段没有交集的段,剪枝掉
对于被要查找的段包住的段,符合要求,回溯
其他的段向下递归,回溯合并的段信息
点修改思路:从根向下递归找叶子,找到需要的点就修改,在回溯中更新段信息
特别的操作1:段修改
对一段元素做同一操作,势必会影响到非叶子节点。
对于像给一段元素加上同一值这样的操作,可以直接给代表这段的节点操作:记录到延时标志上面,并不更新其他的值,把更新操作留到查询操作中解决。
对于段赋值,就需要在对这一段修改前确保这一段处于改了也没问题的状态,即延时标记为0
由于下推操作只有O(1)的复杂度所以可以到处用
所以在进行修改前下推一下就行了
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000;
//用于存放线段树的结构 l,r表示此区间的左右端点
struct segn
{
int data,l,r,mi,su,mark_add;
}segt[maxn];
//建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息
void push_dowm(int root)
{
if(segt[root].mark_add!=0)
{
int st=segt[root].l;
int en=segt[root].r;
segt[root].su+=segt[root].mark_add*(en-st+1);
segt[root].mi+=segt[root].mark_add;
segt[root*2+1].mark_add+=segt[root].mark_add;
segt[root*2+2].mark_add+=segt[root].mark_add;
segt[root].mark_add=0;
}
}
void build(int *dat,int root,int st,int en)
{
//st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据
//记录左右端点
segt[root].l=st;
segt[root].r=en;
if(st==en)//在叶子节点上加入元素数据
{
segt[root].data=dat[st];
segt[root].mark_add=0;
segt[root].mi=segt[root].data;//求区间最小值的操作
segt[root].su=segt[root].data;//求区间和的操作
return;
}
//对于非叶子节点
int mid=(st+en)/2;//分割点
//由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2
build(dat,root*2+1,st,mid);//递归建左子树
build(dat,root*2+2,mid+1,en);//右子树
//由左右子树得到此节点的数据
//求区间最小值
segt[root].mi=min(segt[root*2+1].mi,segt[root*2+2].mi);
//求区间和
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
}
/*
//查询的模板
int query_(int qst,int qen,int tst,int ten,int root)
{//查询子段和
push_dowm(root);
if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
return 一个对合并值没有效果的值;
if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
return segt[root].数据;
int mid =(tst+ten)/2;
return 子段合并操作//搜索左右子树
}
*/
const int bigmun=1e7;
//下推操作在查询中完成
int query_mi(int qst,int qen,int tst,int ten,int root)//查询操作:向下递归的找区间,只要找到被要找的区间包括的区间,就可以肯定这个区间是需要的(因为向下递归的顺序和不交叉的顺序)
{//查询最小值
//qst,qen:想查询的区间 tst,ten:正在查的区间
push_dowm(root);
if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
return bigmun;
if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
return segt[root].mi;
int mid =(tst+ten)/2;
return min(query_mi(qst,qen,tst,mid,root*2+1),query_mi(qst,qen,mid+1,ten,root*2+2));//搜索左右子树
}
int query_sum(int qst,int qen,int tst,int ten,int root)
{//查询子段和
push_dowm(root);
if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
return 0;
if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
return segt[root].su;
int mid =(tst+ten)/2;
return query_sum(qst,qen,tst,mid,root*2+1)+query_sum(qst,qen,mid+1,ten,root*2+2);//搜索左右子树
}
void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval
{//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍
if(st==en)
{
if(st==pos)
{//在这里进行元素修改
segt[root].data+=addval;
segt[root].mi+=addval;//最小值
segt[root].su+=addval;//子段和
}
return;
}
int mid=(st+en)/2;
if(pos<=mid)
adjust_elemt(addval,pos,st,mid,root*2+1);
else
adjust_elemt(addval,pos,mid+1,en,root*2+2);
//在这里进行段修改
segt[root].mi=min(segt[root*2+1].mi,segt[root*2+2].mi);//最小值段修改
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;//子段和段修改
}
void segadd(int qst,int qen,int tst,int ten,int root,int addnum)
{//全段加
if(qst>ten||qen<tst)//不符合的区间
{
return ;
}
if(tst>=qst&&ten<=qen)//找到要加的区间
{
segt[root].mark_add+=addnum;
return ;
}
int mid =(tst+ten)/2;
if(tst>qst&&ten>qen)segt[root].su+=addnum*(qen-tst+1)*addnum;
else if(tst<qst&&ten<qen)segt[root].su+=addnum*(ten-qst+1)*addnum;
else segt[root].su+=addnum*(qen-qst+1);
segadd(qst,qen,tst,mid,root*2+1,addnum);
segadd(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树
// segt[root].mark_add+=addthis;
// return addthis;
}
int main()
{
const int sz=17;
int arr[sz+1];
for(int i=0;i<sz+1;i++)arr[i]=1;
build(arr,0,0,sz);
int j=2,cur;
// adjust_elemt(-1000,4,0,50,0);
segadd(0,3,0,sz,0,-1);
for(int i=0;i<=sz;i++)query_mi(i,i,0,sz,0);
for(int i=0;i<sz*2+1;i++)
{
cout<<segt[i].l<<"~"<<segt[i].r<</*":segmin:"<<segt[i].mi<<*/" segsum:"<<segt[i].su<</*" segmark:"<<segt[i].mark_add<<*/" ";
if(i+2==j)
{
cout<<endl;
j*=2;
}
}
//测试build
//cout<<query_mi(5,10,0,50,0);
//cout<<query_sum(0,2,0,50,0);
return 0;
}
hdu 1166 敌兵布阵
直接套模板的题,然而神志不清的手滑了半天
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=200000;
int sz=1;
//用于存放线段树的结构 l,r表示此区间的左右端点
struct segn
{
int data,l,r,mi,su,mark_add;
}segt[maxn];
//建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息
void build(int *dat,int root,int st,int en)
{
//st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据
//记录左右端点
if(st==en)//在叶子节点上加入元素数据
{
segt[root].su=dat[st];//求区间和的操作
return;
}
//对于非叶子节点
int mid=(st+en)/2;//分割点
//由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2
build(dat,root*2+1,st,mid);//递归建左子树
build(dat,root*2+2,mid+1,en);//右子树
//由左右子树得到此节点的数据
//求区间最小值
//求区间和
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
}
int query_sum(int qst,int qen,int tst,int ten,int root)
{//查询子段和
if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
return 0;
if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
return segt[root].su;
int mid =(tst+ten)/2;
return query_sum(qst,qen,tst,mid,root*2+1)+query_sum(qst,qen,mid+1,ten,root*2+2);//搜索左右子树
}
void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval
{//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍
/* if(en>sz)
{
int k=0;
while(++k){k=1;}
}*/
if(st==en)
{
if(st==pos)
{//在这里进行元素修改
segt[root].su+=addval;//子段和
}
return;
}
int mid=(st+en)/2;
if(pos<=mid)
adjust_elemt(addval,pos,st,mid,root*2+1);
else
adjust_elemt(addval,pos,mid+1,en,root*2+2);
//在这里进行段修改
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;//子段和段修改
}
int main()
{
int t,n,arr[65536],counn=0;
string comm;
scanf("%d",&t);
while(t--)
{
bool flag=true;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&arr[i]);
}
sz=n;
build(arr,0,0,sz-1);
//for(int i=0;i<2*sz;i++)cout<<segt[i].su<<" ";
int st,en,ad;
cout<<"Case "<<++counn<<":"<<endl;
while(cin>>comm)
{
if(comm=="End")break;
if(comm=="Query")
{
scanf("%d%d",&st,&en);
cout<<query_sum(st-1,en-1,0,sz-1,0)<<endl;
continue;
}
if(comm=="Add"){scanf("%d%d",&st,&ad);}
if(comm=="Sub"){scanf("%d%d",&st,&ad);ad=-ad;}
adjust_elemt(ad,st-1,0,sz-1,0);
}
}
return 0;
}
hdu 1754
I Hate It
同样是模板题
#include <cstdio>
#include<iostream>
using namespace std;
const int maxn=200000*4;
int Max(int x,int y){return x>=y ? x:y;}
//用于存放线段树的结构 l,r表示此区间的左右端点
struct segn
{
int data,l,r,mi,su,mark_add;
}segt[maxn];
//建树:叶节点终止条件->递归建左右子树->由左右孩子信息得到此节点信息
void build(int *dat,int root,int st,int en)
{
//st,en表示目前区间的首尾端点序号,dat[]表示用于初始化的数据
//记录左右端点
if(st==en)//在叶子节点上加入元素数据
{
segt[root].mi=dat[st];//求区间最小值的操作
return;
}
//对于非叶子节点
int mid=(st+en)/2;//分割点
//由二叉性,数组存树的左右节点编号分别为此节点的*2+1和*2+2
build(dat,root*2+1,st,mid);//递归建左子树
build(dat,root*2+2,mid+1,en);//右子树
//由左右子树得到此节点的数据
//求区间最小值
segt[root].mi=Max(segt[root*2+1].mi,segt[root*2+2].mi);
//求区间和
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
}
int query_mi(int qst,int qen,int tst,int ten,int root)//查询操作:向下递归的找区间,只要找到被要找的区间包括的区间,就可以肯定这个区间是需要的(因为向下递归的顺序和不交叉的顺序)
{//查询最小值
//qst,qen:想查询的区间 tst,ten:正在查的区间
if(qst>ten||qen<tst)//想查的区间和正在查区间的没有重叠,剪枝
return 0;
if(tst>=qst&&ten<=qen)//正在查的区间比想查的小或者相等,符合
return segt[root].mi;
int mid =(tst+ten)/2;
return Max(query_mi(qst,qen,tst,mid,root*2+1),query_mi(qst,qen,mid+1,ten,root*2+2));//搜索左右子树
}
void adjust_elemt(const int addval,const int pos,int st,int en,int root)//点修改,给pos位置的元素加上addval
{//思路:递归一遍,查找并修改需要修改的元素,然后把路径更新一遍
if(st==en)
{
if(st==pos)
{//在这里进行元素修改
segt[root].mi=addval;//最大值
}
return;
}
int mid=(st+en)/2;
if(pos<=mid)
adjust_elemt(addval,pos,st,mid,root*2+1);
else
adjust_elemt(addval,pos,mid+1,en,root*2+2);
//在这里进行段修改
segt[root].mi=Max(segt[root*2+1].mi,segt[root*2+2].mi);//最大值段修改
}
int main()
{
int xss,czs;
while(~scanf("%d%d",&xss,&czs))
{
int arr[200005],cur;
for(int i=0;i<xss;i++)
{
scanf("%d",&cur);
arr[i]=cur;
}
build(arr,0,0,xss-1);
//for(int i=0;i<2*xss;i++)cout<<segt[i].mi<<" ";
while(czs--)
{
char c[4];
int st,en;
scanf("%s",c);//cout<<"_____"<<c<<"______";
if(c[0]=='Q')
{
scanf("%d%d",&st,&en);
printf("%d\n",query_mi(st-1,en-1,0,xss-1,0));
}
else
{
scanf("%d%d",&st,&en);
adjust_elemt(en,st-1,0,xss-1,0);
// for(int i=0;i<2*xss;i++)cout<<endl<<segt[i].mi<<" ";
}
}
}
return 0;
}
2017.11.14
做题的时候发现模板前面的代码写的太烂,一方面是多余的东西太多,一方面是段修改写错了
拿hdu1698的代码改改补个有段操作的新模板(点查询还没写)(段最值操作也没写)
#include <cstdio>
#include<iostream>
using namespace std;
const int maxn=500100;
struct segn
{
int su,mark_add;
}segt[maxn];
//下推的root是正在改的点的位置,st,en是这个点的区间
void push_dowm_segadj(int root,int st,int en)//段加的下推
{
if(segt[root].mark_add!=0)
{
segt[root*2+1].su=((st+en)/2-st+1)*segt[root].mark_add;
segt[root*2+2].su=(en-(st+en)/2)*segt[root].mark_add;
segt[root*2+1].mark_add=segt[root].mark_add;
segt[root*2+2].mark_add=segt[root].mark_add;
segt[root].mark_add=0;
}
}
void push_dowm_segadd(int root,int st,int en)//段赋值的下推
{
if(segt[root].mark_add!=0)
{
segt[root*2+1].su+=((st+en)/2-st+1)*segt[root].mark_add;
segt[root*2+2].su+=(en-(st+en)/2)*segt[root].mark_add;
segt[root*2+1].mark_add+=segt[root].mark_add;
segt[root*2+2].mark_add+=segt[root].mark_add;
segt[root].mark_add=0;
}
}
void build(int *arr,int root,int st,int en)//arr:赋值数组 root,st写0 en写arr的长度-1
{
if(st==en)
{
segt[root].mark_add=0;
segt[root].su=arr[st];
return;
}
int mid=(st+en)/2;
build(arr,root*2+1,st,mid);
build(arr,root*2+2,mid+1,en);
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
}
void segadj(int qst,int qen,int tst,int ten,int root,int addnum)
{//段赋值 q:查询区间 t:正在搞的区间,写0和n-1 addnum;改成的值
if(qst>ten||qen<tst)//不符合的区间
{
return ;
}
if(tst>=qst&&ten<=qen)//找到要改的区间
{
segt[root].mark_add=addnum;
segt[root].su=addnum*(ten-tst+1);
return ;
}
int mid =(tst+ten)/2;
push_dowm_segadj(root,tst,ten);
segadj(qst,qen,tst,mid,root*2+1,addnum);
segadj(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树
}
void segadj(int qst,int qen,int tst,int ten,int root,int addnum)
{//段修改 q:查询区间 t:正在搞的区间,写0和n-1 addnum;改成的值
if(qst>ten||qen<tst)//不符合的区间
{
return ;
}
if(tst>=qst&&ten<=qen)//找到要加的区间
{
segt[root].mark_add=addnum;
segt[root].su=addnum*(ten-tst+1);
return ;
}
int mid =(tst+ten)/2;
push_dowm_segadd(root,tst,ten);
segadd(qst,qen,tst,mid,root*2+1,addnum);
segadd(qst,qen,mid+1,ten,root*2+2,addnum);//搜索左右子树
}
/*
void upd(int root,int st,int en)//对于全部改完后才进行查询的题,可以最后进行整体下推
{
if(st==en)return;
push_dowm(root,st,en);//按需求改这个下推
upd(2*root+1,st,(st+en)/2);
upd(2*root+2,(st+en)/2+1,en);
segt[root].su=segt[root*2+1].su+segt[root*2+2].su;
}
*/