先上几道线段树的题目,AC代码我放在最后,大家可以先直接看一眼题目,自己想想思路
https://nanti.jisuanke.com/t/237
http://acm.hdu.edu.cn/showproblem.php?pid=1166
http://acm.hdu.edu.cn/showproblem.php?pid=1394
首先,线段树是什么?
神奇海螺回答:线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
不明白没关系,先把上面的三个题目看一下,找一下他们的共同点。
是不是它们都有类似这样的特点:有一个从1到n的区间(例如地主记账从1到n编号,军队按照河岸驻扎从1到n编号)
所以知道为啥叫 “线段树” 了8;
然后让你求修改某点的值,或者求某个区间的属性,而且是多次求。
可以怎么求?直接for循环不就完了!?求一次for一次,时间复杂度呢,O(N),超时没商量;
这时候线段树的优势就就体现出来了。
停!! 线段树还没弄明白是啥呢,怎么就体现优势了
那线段树到底是个啥树呢?有请神奇海螺:
首先,一棵树每个节点必须要有三个或以上的属性,其中两个是左端点、右端点,另一个就是你需要用的值啦,比如求的和,最小值等等等等。
如下图:给你一个1~13区间,【i,j】分别代表左右端点,根节点就是【1,13】,然后建左子树、右子树。
最终在叶子处分成单个点。
建树代码:(你可以使用指针,也可以用数组模拟,注意数组模拟的时候把数组开到 数据个数的四倍 才不会溢出)
void build_tree(int l,int r,int k){ //建树是所有线段树的第一步
tree[k].l=l;
tree[k].r=r;
if(l==r) return ;
int mid=(l+r)/2;
build_tree(l,mid,k*2);
build_tree(mid+1,r,k*2+1);
}
好了,建树过程就完了,然后就怎么去使用的问题了。
这里就说一下最常用的几个使用,至于其他的还需要在题目中补充啦。
First:修改某个点的值:
void update(int i,int t,int num){
if(tree[t].l==i&&tree[t].r==i) { //找到这个节点后进行修改
tree[t].sum+=num;
return;
}
int mid=(tree[t].l+tree[t].r)/2;
if (mid>=i) update(i,2*t,num); //向左
else update(i,2*t+1,num); //向右
tree[t].sum=tree[2*t].sum+tree[2*t+1].sum; //修改完叶子的要更新父节点的
}
Second:
求某个区间的XXX:
int ss;
void search(int l,int r,int t){
if(tree[t].l>=l&&tree[t].r<=r){
ss+=tree[t].sum;
return ;
}
int mid=(tree[t].l+tree[t].r)>>1;
//仔细想一下下面这几行
if(r<=mid) search(l,r,t*2);
else if(l>mid) search(l,r,t*2+1);
else{
search(l,mid,t*2);
search(mid+1,r,t*2+1);
}
}
至于其他的想起来再更~~
下面是三道题的AC代码(可以参考下)
第一题:
#include<bits/stdc++.h>
using namespace std;
const int maxn= 100005;
struct node{
int l;
int r;
int minv,minp;
}tree[maxn*4];
void build_tree(int l,int r,int k){ //建树是所有线段树的第一步
tree[k].l=l;
tree[k].r=r;
if(l==r) return ;
int mid=(l+r)/2;
build_tree(l,mid,k*2);
build_tree(mid+1,r,k*2+1);
}
void update(int k,int i,int data){
int l=tree[k].l,r=tree[k].r;
if(i==l&&i==r){
tree[k].minv=data;
return;
}
int mid=(l+r)/2;
if(i<=mid) update(2*k,i,data);
else update(2*k+1,i,data);
tree[k].minv=min(tree[2*k].minv,tree[2*k+1].minv);
// tree[k].minp=(tree[2*k].minv<tree[2*k+1].minv?tree[2*k].minp:tree[2*k+1].minp);
}
int ans,p;
void search(int k,int l,int r){
int left=tree[k].l,right=tree[k].r;
if(l==left&&r==right){
ans=min(ans,tree[k].minv);
return;
}
int mid=(left+right)/2;
if(r<=mid) search(k*2,l,r);
else if(l>mid) search(k*2+1,l,r);
else{
search(k*2,l,mid);
search(k*2+1,mid+1,r);
}
}
int main()
{
memset(tree,0,sizeof(tree));
int m,n,temp;
while(cin>>m>>n){
build_tree(1,m,1);
for(int i=1;i<=m;i++){
cin>>temp;
update(1,i,temp);
}
int l,r;
while(n--){
ans=1e8;
cin>>l>>r;
search(1,l,r);
printf("%d%c",ans,n==0?'\n':' ');
}
}
return 0;
}
第二题:
#include<iostream>
#include<cstring>
using namespace std;
struct node{
int l,r,sum;
};
const int maxn=50005;
node tree[maxn*4];
void buile(int l,int r,int t){
tree[t].l=l;
tree[t].r=r;
tree[t].sum=0;
if(r==l) return;
int mid=(l+r)>>1;
buile(l,mid,t*2);
buile(mid+1,r,t*2+1);
}
void update(int i,int t,int num){
if(tree[t].l==i&&tree[t].r==i) {
tree[t].sum+=num;
return;
}
int mid=(tree[t].l+tree[t].r)/2;
if (mid>=i) update(i,2*t,num);
else update(i,2*t+1,num);
tree[t].sum=tree[2*t].sum+tree[2*t+1].sum;
}
int ss;
void search(int l,int r,int t){
if(tree[t].l>=l&&tree[t].r<=r){
ss+=tree[t].sum;
return ;
}
int mid=(tree[t].l+tree[t].r)>>1;
if(r<=mid) search(l,r,t*2);
else if(l>mid) search(l,r,t*2+1);
else{
search(l,mid,t*2);
search(mid+1,r,t*2+1);
}
}
int main()
{
int t,n,temp,a,b;
string st;
cin>>t;
for (int i=1;i<=t;i++){
printf("Case %d:\n",i);
scanf("%d",&n);
buile(1,n,1);
for (int i=1;i<=n;i++){
scanf("%d",&temp);
update(i,1,temp);
}
while(cin>>st&&st[0]!='E'){
ss=0;
scanf("%d%d",&a,&b);
if(st[0]=='Q'){
search(a,b,1);
printf("%d\n",ss);
}
else if(st[0]=='A'){
update(a,1,b);
}
else if(st[0]=='S'){
update(a,1,-b);
}
}
}
return 0;
}
第三题:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=5010;
int a[maxn];
int n,ans;
struct node{
int left,right,sum;
}t[maxn<<2];
void build(int k,int l,int r)
{
t[k].left=l;t[k].right=r;
t[k].sum=0;
if(l==r) return;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void update(int k,int val)
{
int l=t[k].left,r=t[k].right;
if(l==val && val==r){
t[k].sum++;
return;
}
int mid=(l+r)>>1;
if(val<=mid) update(k<<1,val);
else update(k<<1|1,val);
t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
void query(int k,int l,int r)
{
if(l==t[k].left && r==t[k].right)
{
ans+=t[k].sum;
return;
}
int mid=(t[k].left+t[k].right)>>1;
if(r<=mid) query(k<<1,l,r);
else if(l>mid) query(k<<1|1,l,r);
else{
query(k<<1,l,mid);
query(k<<1|1,mid+1,r);
}
}
int main()
{
while(scanf("%d",&n) != EOF)
{
build(1,0,n);//虽然只有到n-1,但是由于后面用到a[i]+1,为防止溢出,树区间要到n
ans=0;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
query(1,a[i],n-1);//计算比a[i]大的数的个数。注:这些数是包含于插入a[i]前的
update(1,a[i]);//此时将a[i]插入
}
int temp=ans;
for(int i=0;i<n-2;i++)
{
ans=ans-a[i]+(n-a[i]-1);
temp=min(temp,ans);
}
printf("%d\n",temp);
}
return 0;
}