概括:線段樹是一種數據結構,針對需要動態 修改並且獲取數據中一段信息(子段和,子段最小值等)的數據來使用
大體思路:使用一顆完全二叉樹(數組實現)記錄數據。樹的葉子節點記錄每一個數據元素,非葉子節點記錄段信息。
建樹,查詢,修改都由二叉樹的性質:第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;
}
*/