线段树全面学习 (慢慢更新)

线段树的介绍以后再补充,先给算法题型分类吧!

首先明确一下,如果难以转化或者满足区间加和问题,那么使用线段树就很难解决问题,所以推荐使用

离线的莫队算法(不支持复杂的修改): 对查询的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

三、拓展的线段树:二维线段树

 

你可能感兴趣的:(#,线段树)