经过一星期的学习,线段树算是摸到了皮毛,大致学会了单点更新与区间更新,区间更新感觉还是有点略难
,学到新东西还是很快乐的
定义
还是定义先行。线段树作为高级数据结构,要想了解基本内容,需要对数据结构的树有一定的了解。线段树属于二叉搜索树,将一个大区间进行划分,划分为单元小区间,每个单元区间又对应树中的一个叶子节点。
如果感觉难以理解,那么希望下面这张图有助于理解。对于每个非叶子节点,左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b],子节点的数目即为整个线段区间的长度。
性质
- 时间复杂度: O(logN),较小,可以快速查询某一结点出现的次数,查询区间长度等很方便
- 空间复杂度: 2N,较大,并且在实际应用中需要开4倍的数组防止出现越界(这个很重要,我又一次就因为这个没注意,wa了一次?,找半天没找到原因)
基本用法
- 单点更新、替换…
- 成段替换、区间更新…
- 区间求和、区间操作…
以上是我这个周学到的基本用法(太菜了orz)
例题
单点:
hdu1166:敌兵布阵
hdu 1754:I Hate It
区间:
hdu 1698: Just a Hook
poj 3468: A Simple Problem with Integers
直接上代码吧orz(按顺序)
敌兵布阵
#include<iostream>
using namespace std;
int tree[200000];
//建树
void BuildTree(int l,int r,int k) {
int mid;
if(l == r) {
scanf("%d",&tree[k]);
return ;
}
mid = (l+r)/2;
BuildTree(l,mid,2*k); //左儿子
BuildTree(mid+1,r,2*k+1); //右儿子
tree[k] = tree[2*k]+tree[2*k+1]; //初始化父代节点
}
//单点更新
void AddNode(int l,int r,int k,int a,int b) {
int mid;
if(l==r){
tree[k]+=b; //单点操作
return ;
}
mid = (l+r)/2;
if(a<=mid){
AddNode(l,mid,2*k,a,b); //左子树单点更新
}
else{
AddNode(mid+1,r,2*k+1,a,b);
}
tree[k] = tree[2*k]+tree[2*k+1];
}
//查询
int Query(int l,int r,int k,int a,int b) {
int mid;
if(a<=l && b>=r){
return tree[k];
}
mid = (l+r)/2;
int ans = 0;
if(a<= mid){
ans += Query(l,mid,2*k,a,b);
}
if(b>mid) {
ans += Query(mid+1,r,2*k+1,a,b);
}
return ans;
}
int main(){
int Case,t;
char str[11];
scanf("%d",&t);
for(Case = 1;Case<=t;Case++){
int n;
scanf("%d",&n);
BuildTree(1,n,1);
printf("Case %d:\n",Case);
while(~scanf("%s",str)){
if(str[0] == 'E'){
break;
}
int x,y;
scanf("%d%d",&x,&y);
if(str[0] == 'Q'){
int ans = Query(1,n,1,x,y);
cout << ans << endl;
}
if(str[0] == 'S'){
AddNode(1,n,1,x,-y);
}
if(str[0] == 'A'){
AddNode(1,n,1,x,y);
}
}
}
return 0;
}
i hate it
#include<iostream>
#include<string.h>
using namespace std;
int tree[400010];
//建树
void BuildTree(int l,int r,int k) {
int mid;
if(l == r) {
scanf("%d",&tree[k]);
return ;
}
mid = (l+r)>>1;
BuildTree(l,mid,k<<1); //左儿子
BuildTree(mid+1,r,k<<1|1); //右儿子
tree[k] = max(tree[k<<1],tree[k<<1|1]); //初始化父代节点
}
//单点更新
void AddNode(int l,int r,int k,int a,int b) {
int mid;
if(l==r){
tree[k]=b; //单点操作
return ;
}
mid = (l+r)>>1;
if(a<=mid){
AddNode(l,mid,k<<1,a,b); //左子树单点更新
}
else{
AddNode(mid+1,r,k<<1|1,a,b);
}
tree[k] = max(tree[k<<1],tree[k<<1|1]);
}
//查询
int Query(int l,int r,int k,int a,int b) {
int mid;
if(a<=l && b>=r){
return tree[k];
}
mid = (l+r)>>1;
int ans = 0;
if(a<= mid){
ans = max(ans,Query(l,mid,k<<1,a,b)) ;
}
if(b>mid){
ans = max(ans,Query(mid+1,r,k<<1|1,a,b));
}
return ans;
}
int main(){
int n,m;
char str[2];
while(~scanf("%d%d",&n,&m)){
BuildTree(1,n,1);
int x,y;
for(int i = 1;i<=m;i++){
scanf("%s%d%d",str,&x,&y);
if(str[0]=='U'){
AddNode(1,n,1,x,y);
}
else{
int ans = Query(1,n,1,x,y);
printf("%d\n",ans);
}
}
}
return 0;
}
区间更新:区间更新比单点更新难以理解,而且也多了一个lazy数组操作,看见lazy你就会明白,这个数组存在的意义就在于,懒,所有还有一个名字(懒惰标记或延迟更新标记)。每次更新到区间后,就会存放在lazy数组里,等到需要往下更新更小区间的时候,就把两次的值一起更新下去.
Just a Hook
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1e5+5;
int lazy[maxn*4];
int tree[maxn*4];
void Push_Down(int l,int r,int k){
if(lazy[k]){
lazy[k<<1]=lazy[k];
lazy[k<<1|1]=lazy[k];
int mid=(l+r)>>1;
tree[k<<1]=(mid-l+1)*lazy[k];
tree[k<<1|1]=(r-mid)*lazy[k];
lazy[k]=0;
}
}
void BuildTree(int l,int r,int k){
if(l==r){
tree[k]=1;
return ;
}
int mid=(l+r)>>1;
BuildTree(l,mid,k<<1);
BuildTree(mid+1,r,k<<1|1);
tree[k]=tree[k<<1]+tree[k<<1|1];
}
void AddNode(int l,int r,int k,int a,int b,int flag){
if(a<=l&&b>=r){
lazy[k]=flag;
tree[k]=(r-l+1)*flag;
return;
}
else{
int mid=(l+r)>>1;
Push_Down(l,r,k);
if(a<=mid)AddNode(l,mid,k<<1,a,b,flag);
if(b>mid)AddNode(mid+1,r,k<<1|1,a,b,flag);
tree[k]=tree[k<<1]+tree[k<<1|1];
}
}
int main(){
int t,n,m;
int x,y,z;
scanf("%d",&t);
for(int k=1;k<=t;k++){
scanf("%d",&n);
memset(lazy,0,sizeof(lazy));
BuildTree(1,n,1);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
AddNode(1,n,1,x,y,z);
}
printf("Case %d: The total value of the hook is %d.\n",k,tree[1]);
}
return 0;
}
A Simple Problem with Integers
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1e6+5;
long long n,m,tree[maxn<<2],lazy[maxn<<2],ans;
void Push_Down(int l,int r,int k){
int mid = (l + r)>>1;
lazy[k<<1] += lazy[k];
lazy[k<<1|1] += lazy[k];
tree[k<<1] += (mid - l + 1)*lazy[k];
tree[k<<1|1] += (r - mid)*lazy[k];
lazy[k] = 0;
}
void BuildTree(int l,int r,int k){
if(l==r){
scanf("%lld",&tree[k]);
return;
}
int mid = (l + r)>>1;
BuildTree(l,mid,k<<1);
BuildTree(mid+1,r,k<<1|1);
tree[k] = tree[k<<1] + tree[k<<1|1];
}
void Query(int a,int b,int l,int r,int k){
if(a<=l&&r<=b){
ans+=tree[k];
return;
}
if(lazy[k]){
Push_Down(l,r,k);
}
int mid = (l+r)>>1;
if(a<=mid){
Query(a,b,l,mid,k<<1);
}
if(mid<b){
Query(a,b,mid+1,r,k<<1|1);
}
}
void AddNode(int a,int b,int c,int l,int r,int k){
if(l>=a&&r<=b){
tree[k] += (r-l+1)*c;
lazy[k] += c;
return;
}
if(lazy[k]){
Push_Down(l,r,k);
}
int mid = (l + r)>>1;
if(a<=mid){
AddNode(a,b,c,l,mid,k<<1);
}
if(b>mid){
AddNode(a,b,c,mid+1,r,k<<1|1);
}
tree[k] = tree[k<<1] + tree[k<<1|1];
}
int main(){
long long n,m;
scanf("%lld%lld",&n,&m);
BuildTree(1,n,1);
char str[2];
for(int i = 1;i <= m;i++){
cin >> str;
if(str[0]=='Q'){
int a,b;
scanf("%d%d",&a,&b);
ans = 0;
Query(a,b,1,n,1);
printf("%lld\n",ans);
}
else if(str[0]=='C'){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
AddNode(a,b,c,1,n,1);
}
}
return 0;
}
一口气看完后,有没有觉得这些代码长得很像?没错他们真的长得很像,线段树四道题写完,第一感受,线段树的简单操作模式还是相对固定的,板子直接改。第二感受,好像没有那么好改(还是我太菜了),bug改起来很难受,真的很难受。写博客的时候看到了一篇很好的博客,给大家分享一下,博主讲的很清楚明白。
传送门
以后还有学习的有关知识,我会努力挖坑填坑的。