线段树的介绍以后再补充,先给算法题型分类吧!
首先明确一下,如果难以转化或者满足区间加和问题,那么使用线段树就很难解决问题,所以推荐使用
离线的莫队算法(不支持复杂的修改): 对查询的q个区间进行排序
以及 在线的分块算法.
一 、简单点更新,区间查询的线段树问题
这里以区间和为例: hdu1166
#include
using namespace std;
const int maxn = 5e4+5;
int sum[maxn*4],a[maxn];
int n,t,cnt = 1;
void pushTo(int rt){
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void build(int l ,int r,int rt)
{
if(l == r)
sum[rt] = a[l];
else{
int m = (l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushTo(rt);
}
}
int Query(int L,int R,int l,int r,int rt) //坑多 注意条件的判断
{
if(L<=l&&r<=R)
return sum[rt];
int ans = 0,m = (l+r)>>1;
if(L<=m)
ans+= Query(L,R,l,m,rt<<1);
if(R>m)
ans+= Query(L,R,m+1,r,rt<<1|1);
return ans;
}
void change(int dt,int v,int l,int r,int rt,bool op){
if(l == r)
{
if(op)
sum[rt] += v;
else
sum[rt] -= v;
}
else{
int m = (l+r)>>1;
if(dt<=m)
change(dt,v,l,m,rt<<1,op);
else
change(dt,v,m+1,r,rt<<1|1,op);
pushTo(rt);
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i = 1;i<=n;i++)
scanf("%d",&a[i]);
char s[7];
cout<<"Case "<
这是最基本的单点修改
二、区间修改,那么我们需要引入延迟标记 来提高我们的算法效率,大体思想是标记一下更新的区间 ,如果下次需要查询的时候,再沿路更新,否则一次性更新的复杂度不只是O(logn)的时间
再来个题目,上代码:poj3465
#include
#define ll long long
using namespace std;
const int maxn = 1e5 +10;
ll a[maxn],s[4*maxn],lazy[4*maxn];
int n,m;
void pushUp(int rt){
s[rt] = s[rt<<1] + s[rt<<1|1];
}
void pushDown(int rt,int ln,int rn){
if(lazy[rt])
{
lazy[rt<<1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
s[rt<<1] += lazy[rt]*ln;
s[rt<<1|1] += lazy[rt]*rn;
lazy[rt] = 0;
}
}
void build(int l,int r,int rt)
{
if(l == r)
s[rt] = a[l];
else{
int mid = (l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
pushUp(rt);
}
}
ll query(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R)
return s[rt];
else{
int mid = (l+r)>>1;
pushDown(rt,mid-l+1,r-mid);
ll ans = 0;
if(L<=mid)
ans += query(L,R,l,mid,rt<<1);
if(R>mid)
ans += query(L,R,mid+1,r,rt<<1|1);
return ans;
}
}
void Update(int L,int R,int v,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
s[rt] += v*(r-l+1);
lazy[rt] += v;
}else{
int mid = (l+r)>>1;
pushDown(rt,mid-l+1,r-mid);
if(L<=mid)
Update(L,R,v,l,mid,rt<<1);
if(R>mid)
Update(L,R,v,mid+1,r,rt<<1|1);
pushUp(rt);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i<=n;i++)
scanf("%lld",&a[i]);
build(1,n,1);
for(int i = 1;i<=m;i++)
{
char c[2];
int x,y,z;
scanf("%s",c);
if(c[0] == 'Q')
{
scanf("%d%d",&x,&y);
printf("%lld\n",query(x,y,1,n,1));
}else if(c[0] == 'C')
{
scanf("%d%d%d",&x,&y,&z);
Update(x,y,z,1,n,1);
}
}
}
到这里,我们基本能写出很多关于线段树的题目了,只要多写就能慢慢理解和运用。
例:hdu4027
这题有点不一样 因为区间更新不再是一次性更新了 而是与每个元素相关 ,按理说递归更新到叶子节点是会超时的
但是发现在long long范围里面最多开7次平方根就能都变成一了 ,所以对于一颗子树,总的更新操作不会超过7次,所以剪枝就好了,采坑记录:格式错误,需要注意的是,更新操作需要递归到叶子才能开根号,而不像求和求最值那样找到子区间就能更新了,慢慢体会一下,有些不同
#include
#include
#include
using namespace std;
#define ll long long
const int maxn = 1e5 + 10;
ll a[maxn], s[maxn<<2],lazy[maxn<<2];
int n,k;
void pushUp(int rt)
{
s[rt] = s[rt<<1] + s[rt<<1|1];
}
void build(int l,int r,int rt)
{
if(l ==r )
s[rt] = a[l];
else{
int m = (l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushUp(rt);
}
}
void Update(int L,int R,int l,int r,int rt){
if(l == r)
{
s[rt] = sqrt(s[rt]);
}else{
int m = (l+r)>>1;
if(L<=l&&r<=R&&s[rt] == r-l+1)//关键剪枝 如果这个区间都变为1了 开方就没意义了
return ;
if(L<=m)
Update(L,R,l,m,rt<<1);
if(R>m)
Update(L,R,m+1,r,rt<<1|1);
pushUp(rt);
}
}
ll query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return s[rt];
}else{
int m = (l+r)>>1;
ll ans = 0;
if(L<=m)
ans+=query(L,R,l,m,rt<<1);
if(R>m)
ans+=query(L,R,m+1,r,rt<<1|1);
return ans;
}
}
int main()
{
int cnt = 1;
while(scanf("%d",&n)!=EOF)
{
for(int i = 1;i<=n;i++)
scanf("%lld",&a[i]);
scanf("%d",&k);
build(1,n,1);
int num = 0;
while(k--)
{
int t,x,y;
scanf("%d%d%d",&t,&x,&y);
int xx=min(x,y),yy = max(x,y);
if(t == 0)
Update(xx,yy,1,n,1);
else{
ll ans = query(xx,yy,1,n,1);
a[num++] = ans;
}
}
printf("Case #%d:\n",cnt++);
for(int i = 0;i
区间查询:
给你n堆石头 ,每次询问区间[l,r]这几堆,然后俩个人轮流拿石头 ,只能拿走一堆中的任意个数,问输赢加权次数和的值
(18年河南省赛c题):链接 https://pan.baidu.com/s/1-z3KSKF06WfYj8RuIcb08g
#include
typedef long long ll;
#define mod 1000000007
const int maxn = 1e6+10;
ll n,m,a[maxn],sum[maxn<<2];
ll pow(ll x,ll y){
ll ans = 1;
ll d = x;
while(y){
if(y&1)
ans = ans*d%mod;
d = d*d%mod;
y>>=1;
}
return ans;
}
void pushUp(int rt){
sum[rt] = sum[rt<<1]^sum[rt<<1|1];
}
void build(int rt,int l,int r){
if(l==r){
sum[rt] = a[l];
}else{
int mid = (l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushUp(rt);
}
}
ll query(int l,int r,int L,int R,int rt){
if(L<=l&&r<=R){
return sum[rt];
}else{
int mid = (l+r)>>1;
ll ans = 0;
if(L<=mid)
ans = ans^query(l,mid,L,R,rt<<1);
if(R>mid)
ans = ans^query(mid+1,r,L,R,rt<<1|1);
return ans;
}
}
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%lld%lld",&n,&m);
for(int i = 1;i<=n;i++)
scanf("%lld",&a[i]);
ll ans = 0;
int x,y;
build(1,1,n);
for(int i = 1;i<=m;i++){
scanf("%d%d",&x,&y);
bool value = query(1,n,x,y,1)==0?0:1;
ans = (ans+value*pow(2,m-i))%mod;//加权求和
}
printf("%lld\n",ans);
}
}
百度之星比赛 板子题
对题目稍加分析就能转换成线段树的问题
题目链接
#include
#include
using namespace std;
#define Max 100005
struct node{
int cnt;
char val;
node(){
cnt = 0;
val = ' ';
}
}num[Max<<2],vals;
char s[Max];
int n,q;
void pushTo(int rt){
if(num[rt<<1].valnum[rt<<1|1].val){
num[rt].cnt = num[rt<<1|1].cnt;
num[rt].val = num[rt<<1|1].val;
}else{
num[rt].cnt = num[rt<<1].cnt+num[rt<<1|1].cnt;
num[rt].val = num[rt<<1].val;
}
}
void build(int rt,int l,int r){
if(l == r){
num[rt].cnt = 1;
num[rt].val = s[l];
}else{
int m = (l+r)>>1;
build(rt<<1,l,m);
build(rt<<1|1,m+1,r);
pushTo(rt);
}
}
node query(int L,int R,int l,int r,int rt){
vals.cnt = 0;
vals.val = ' ';
if(L<=l&&r<=R){
return num[rt];
}
int m = (l+r)>>1;
node val1,val2;
if(L<=m)
val1 = query(L,R,l,m,rt<<1);
if(R>m)
val2 = query(L,R,m+1,r,rt<<1|1);
if(val1.val == ' ') return val2;
if(val2.val == ' ') return val1;
if(val1.val == val2.val){
vals.cnt = val1.cnt + val2.cnt;
vals.val = val1.val;
}else if(val1.val>val2.val){
vals.val = val2.val;
vals.cnt = val2.cnt;
}else if(val1.val
三、拓展的线段树:二维线段树