线段树
建树的时间复杂度是O(n),n为根节点对应的区间长度
线段树上更新叶子节点和进行区间分解时间复杂度都是O(log(n))的,线段树的深度最深的O(log(n)),so每次遍历操作都在O(log(n))的内
线段树的核心在于区间或节点的更新,进行区间查询
代码好长,感觉自己要对这些简单模板记住并深入理解,多写写,避免套模板
HDU-1166 敌兵布阵
/*
HDU-1166 敌兵布阵
线段树的单点修改和区间查询
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 50005;
int a[N];
struct node
{
int l,r,maxn,sum;
}tree[N<<2];
void build(int m,int l,int r)
{
tree[m].l = l;
tree[m].r = r;
if(l == r)
{
tree[m].maxn = a[l];//先将每个底层节点的数据存储在a[i]中,建树时在把数据存到线段树中
tree[m].sum = a[l];
return ;
}
int mid = (l+r)>>1;
build(m<<1,l,mid);
build((m<<1)+1,mid+1,r);
//tree[m].maxn = max(tree[m<<1].maxn,tree[(m<<1)+1].maxn);//回溯时对父节点数据进行更新
tree[m].sum = tree[m<<1].sum + tree[(m<<1)+1].sum;
}
void update(int m, int a, int val)
{
if (tree[m].l == a && tree[m].r == a)
{
//tree[m].maxn += val;
tree[m].sum += val;
return;
}
int mid = (tree[m].l + tree[m].r) >> 1;
if (a <= mid)
update(m << 1, a, val);
else
update((m << 1) + 1, a, val);
//tree[m].maxn = max(tree[m << 1].maxn, tree[(m << 1) + 1].maxn);
tree[m].sum = tree[m << 1].sum + tree[(m << 1) + 1].sum;
}
int query_sum(int m, int l, int r)
{
if (l == tree[m].l && r == tree[m].r)
return tree[m].sum;
// return tree[m].maxn;
int mid = (tree[m].l + tree[m].r) >> 1;
if (r <= mid)
return query_sum(m << 1, l, r);
if (l > mid)
return query_sum((m << 1) + 1, l, r);
return query_sum(m << 1, l, mid) + query_sum((m << 1) + 1, mid + 1, r);
//return max(query_max(m << 1, l, mid), query_max((m << 1) + 1, mid + 1, r));
}
int main()
{
int t,n;
cin >> t;
for(int ca = 1;ca <= t;ca++)
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
build(1,1,n);
printf("Case %d:\n",ca);
int a,b;
char str[20];
while(scanf("%s",str) && str[0] != 'E')
{
scanf("%d%d",&a,&b);
if(strcmp(str,"Query")==0)
{
printf("%d\n",query_sum(1,a,b));
}
else if(strcmp(str,"Add")==0)
{
update(1,a,b);
}
else
{
update(1,a,-b);
}
}
}
return 0;
}
树状数组:
#include<bits/stdc++.h>
using namespace std;
const int N = 50005;
int n,c[N];
void update(int x,int val)
{
for(;x <= n;x += (x&-x))
c[x] += val;
}
int sum(int x)
{
int ans = 0;
for(;x > 0;x -= x&(-x))
ans += c[x];
return ans;
}
int main()
{
int t;
cin >> t;
for(int ca = 1;ca <= t;ca++)
{
memset(c,0,sizeof(c));
int x,y;
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%d",&x),update(i,x);
printf("Case %d:\n",ca);
char op[10];
while(true)
{
scanf("%s",op);
if(op[0] == 'E')
break;
else if(op[0] == 'Q')
{
scanf("%d%d",&x,&y);
printf("%d\n",sum(y)-sum(x-1));
}
else if(op[0] == 'A')
{
scanf("%d%d",&x,&y);
update(x,y);
}
else
{
scanf("%d%d",&x,&y);
update(x,-y);
}
}
}
return 0;
}
HDU1754
/*
HDU-1754 I Hate It
线段树的单点修改(替换)和区间查询
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
using namespace std;
const int N = 200005;
const int INF = 0x3f3f3f3f;
int ans;
struct node
{
int l,r,maxn;
}tree[N<<2];
void Build(int l,int r,int rt)
{
tree[rt].l = l;
tree[rt].r = r;
if(l == r)
{
scanf("%d",&tree[rt].maxn);
return;
}
int mid = (l+r)>>1;
Build(l,mid,rt<<1);
Build(mid+1,r,rt<<1|1);
tree[rt].maxn = max(tree[rt<<1].maxn,tree[rt<<1|1].maxn);
}
void update(int x,int y,int rt)
{
if(tree[rt].l == tree[rt].r)
{
tree[rt].maxn = y;
return;
}
int mid = (tree[rt].l+tree[rt].r)>>1;
if(x > mid)//向右
update(x,y,rt<<1|1);
else
update(x,y,rt<<1);
tree[rt].maxn = max(tree[rt<<1].maxn,tree[rt<<1|1].maxn);
}
void Query(int l,int r,int rt)
{
if(tree[rt].l == l && tree[rt].r == r)
{
ans = max(ans,tree[rt].maxn);
return;
}
int mid = (tree[rt].l+tree[rt].r)>>1;
if(l > mid)
Query(l,r,rt<<1|1);
else if(r <= mid)
Query(l,r,rt<<1);
else
{
Query(l,mid,rt<<1);
Query(mid+1,r,rt<<1|1);
}
}
int main()
{
int n,m,x,y;
string name;
while(~scanf("%d%d",&n,&m))
{
Build(1,n,1);
for(int i = 1;i <= m;i++)
{
cin>>name>>x>>y;
if(name == "Q")
{
ans = 0;
Query(x,y,1);
printf("%d\n",ans);
}
else
update(x,y,1);
}
}
return 0;
}
/*
题意:给定Q (1 ≤ Q ≤ 200,000)个数A1,Q,i– Aj中最大数和最小数的差。
求极大值和极小值
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF = 0xffffff0;
int minV = INF;
int maxV = -INF;
struct Node //不要左右子节点指针的做法
{
int L, R;//区间起点和终点
int minV,maxV;//本区间里的最大最小值
int Mid()
{
return (L+R)/2;
}
}tree[800010];//4倍叶子节点的数量就够
void BuildTree(int root , int L, int R)
{
tree[root].L = L;
tree[root].R = R;
tree[root].minV = INF;
tree[root].maxV = - INF;
if(L != R)
{
BuildTree(2*root+1,L,(L+R)/2);
BuildTree(2*root+2,(L+R)/2 + 1, R);
}
}
void Insert(int root, int i,int v)//将第i个数,其值为v,插入线段树
{
if(tree[root].L == tree[root].R)
{
//成立则亦有 tree[root].R == i
tree[root].minV = tree[root].maxV = v;
return;
}
tree[root].minV = min(tree[root].minV,v);
tree[root].maxV = max(tree[root].maxV,v);
if(i <= tree[root].Mid())
Insert(2*root+1,i,v);
else
Insert(2*root+2,i,v);
}
void Query(int root,int s,int e)
{
//查询区间[s,e]中的最小值和最大值,如果更优就记在全局变量里
//minV和maxV里
if( tree[root].minV >= minV && tree[root].maxV <= maxV )
return;
if( tree[root].L == s && tree[root].R == e )
{
minV = min(minV,tree[root].minV);
maxV = max(maxV,tree[root].maxV);
return ;
}
if( e <= tree[root].Mid())
Query(2*root+1,s,e);
else if( s > tree[root].Mid() )
Query(2*root+2,s,e);
else
{
Query(2*root+1,s,tree[root].Mid());
Query(2*root+2,tree[root].Mid()+1,e);
}
}
int main()
{
int n,q,h;
int i,j,k;
scanf("%d%d",&n,&q);
BuildTree(0,1,n);
for( i = 1;i <= n;i ++ )
{
scanf("%d",&h);
Insert(0,i,h);
}
for( i = 0;i < q;i ++ )
{
int s,e;
scanf("%d%d", &s,&e);
minV = INF;
maxV = -INF;
Query(0,s,e);
printf("%d\n",maxV - minV);
}
return 0;
}
题意:给定Q (1 ≤ Q ≤ 100,000)个数A1,A2… AQ,,以及可能多次进行的两个操作:
1. 对某个区间Ai … Aj的每个数都加n(n可变)
2.求某个区间Ai… Aj的数求和
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100005;
struct node
{
ll l,r;
ll add,sum;//addv是懒惰标记
}tree[N<<2];
void pushup(int root)
{
tree[root].sum = tree[root << 1].sum + tree[root << 1 | 1].sum;
}
void pushdown(int root, int m)
{
if (tree[root].add)
{
tree[root << 1].add += tree[root].add;
tree[root << 1 | 1].add += tree[root].add;
tree[root << 1].sum += tree[root].add * (m - (m >> 1));
tree[root << 1 | 1].sum += tree[root].add * (m >> 1);
tree[root].add = 0;
}
}
void build(int l, int r, int root)
{
tree[root].l = l;
tree[root].r = r;
tree[root].add = 0;
if (l == r)
{
scanf("%lld", &tree[root].sum);
return;
}
int mid = (tree[root].l + tree[root].r) / 2;
build(l, mid, root << 1);
build(mid + 1, r, root << 1 | 1);
pushup(root);
}
void update(int c, int l, int r, int root)
{
if (tree[root].l == l && tree[root].r == r)
{
tree[root].add += c;
tree[root].sum += (ll)c * (r - l + 1);
return;
}
if (tree[root].l == tree[root].r)
return;
pushdown(root, tree[root].r - tree[root].l + 1);
int mid = (tree[root].l + tree[root].r) >> 1;
if (r <= mid)
update(c, l, r, root << 1);
else if (l > mid)
update(c, l, r, root << 1 | 1);
else
{
update(c, l, mid, root << 1);
update(c, mid + 1, r, root << 1 | 1);
}
pushup(root);
}
ll query(int l, int r, int root)
{
if (l == tree[root].l && r == tree[root].r)
return tree[root].sum;
pushdown(root, tree[root].r - tree[root].l + 1);
int mid = (tree[root].r + tree[root].l) >> 1;
ll res = 0;
if (r <= mid)
res += query(l, r, root << 1);
else if (l > mid)
res += query(l, r, root << 1 | 1);
else
{
res += query(l, mid, root << 1);
res += query(mid + 1, r, root << 1 | 1);
}
return res;
}
int main()
{
int n,q;
scanf("%d%d",&n,&q);
build(1,n,1);
ll x,y,val;
char str[5];
while(q--)
{
scanf("%s",str);
if(str[0] == 'C')
{
scanf("%lld%lld%lld",&x,&y,&val);
update(val,x,y,1);
}
else
{
scanf("%lld%lld",&x,&y);
printf("%lld\n",query(x,y, 1));
}
}
return 0;
}
树状数组
能快速求任意区间的和,树状数组适合单个元素经常被修改而且还反复要求部分的区间的和的情况,比线段树除了代码少的优势,还有运行效率也更快,因为树状数组常数小
/*
经典问题:二维偏序。
题意:n个星星,让你求每个星星非右、非上的星星个数
输入的x,y已经根据y优先x其次的顺序将大小排序好了
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 15005;
const int M = 32005;
int c[M]={0};//树状数组
int total[N]={0};
int lowbit(int x)
{
return x&(-x);
}
void add(int t,int val)
{
while(t<=M)
{
c[t] += val;
t += lowbit(t);
}
}
int sum(int t)
{
int ans = 0;
while(t > 0)
{
ans += c[t];
t -= lowbit(t);
}
return ans;
}
int main()
{
int n;
scanf("%d",&n);
int x,y;
for(int i = 1;i <= n;i++)
{
scanf("%d%d",&x,&y);
add(x+1,1);
total[sum(x+1)-1]++;
}
for(int i = 0;i < n;i++)
printf("%d\n",total[i]);
return 0;
}