线段树专题

Acwing 245. 你能回答这些问题吗

思路

题目问的是区间[x,y]最大连续子段和tmax

如果用线段树求解,那么就需要维护以下信息

我们可以利用分治的思想,将一个子段分为左右两个最大连续子段和,lmax,rmax;

同时需要维护左右区间的sum;那么根节点 u.lmax = max(u.lmax,左sum+右lmax),同理维护u.rmax

那么任意区间的最大连续和就可以分为三种情况

一种是只在左儿子的tmax,一种是在右儿子的tmax,还有一个就是横跨左右儿子 即 左二子的rmax+右儿子的lmax;

void pushup(Node &u,Node &l,Node &r)
{
    u.sum=l.sum+r.sum;
    u.lmax=max(l.lmax,l.sum+r.lmax);
    u.rmax=max(r.rmax,r.sum+l.rmax);
    u.tmax=max(max(l.tmax,r.tmax),r.lmax+l.rmax);
}

求得最大值即可;

那么线段树只需要用到单点修改和区间查询即可

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 5e+5;
int a[N];
struct Node {
	int l,r;
	int lmax,rmax,tmax,sum;

} tre[N<<2];
void pushup(Node &u,Node &l,Node &r) {
	u.sum=l.sum+r.sum;
	u.lmax=max(l.lmax,l.sum+r.lmax);
	u.rmax=max(r.rmax,r.sum+l.rmax);
	u.tmax=max(max(l.tmax,r.tmax),r.lmax+l.rmax);
}
void pushup(int rt) {
	pushup(tre[rt],tre[rt*2],tre[rt*2+1]);
}
void build(int rt,int l,int r) {
	if(l == r) {
		tre[rt]= {l,r,a[l],a[l],a[l],a[l]};

	} else {
		tre[rt]= {l,r};
		int mid=(l+r)/2;
		build(rt*2,l,mid);
		build(rt*2+1,mid+1,r);
		pushup(rt);
	}
}
Node query(int rt,int l,int r) {
	if(tre[rt].l>=l&&tre[rt].r<=r) {
		return tre[rt];
	}

	else {
		int mid = (tre[rt].l+tre[rt].r)/2;
		if(r<=mid)
			return query(rt*2,l,r);
		else if(l>mid)
			return query(rt*2+1,l,r);
		else {
			auto Left=query(rt*2,l,r);
			auto Right=query(rt*2+1,l,r);
			Node res;
			pushup(res,Left,Right);
			return res;
		}
	}
}
void modify(int rt,int x,int v) {
	if(tre[rt].l==x&&tre[rt].r==x) {
		tre[rt]= {x,x,v,v,v,v};

	} else {
		int mid = (tre[rt].l+tre[rt].r)/2;
		if(x<=mid)
			modify(rt*2,x,v);
		else
			modify(rt*2+1,x,v);
		pushup(rt);
	}
}
signed main() {
	int n,m;
	cin >> n >> m;
	for(int i=1; i<=n; i++) {
		cin >> a[i];
	}
	build(1,1,n);

	while (m -- ) {
		int k,x,y;
		cin >> k >> x >> y;


		if(k == 1) {
			if(x>y)
				swap(x,y);
			Node ans = query(1,x,y);
			cout<<ans.tmax<<endl;
		} else {
			modify(1,x,y);
		}
	}
	return 0;
}

246. 区间最大公约数

如果想要维护一个区间的gcd,并进行区间修改的操作很难,但如果根据gcd的这个性质

gcd(a,b)=gcd(a,b−a)

便可以用线段树维护差分去做,区间修改就很容易了,只需要单点修改左右端点即可

重点是如何用线段树利用差分信息去求区间[l,r]的gcd

根据gcd(a,b,c)=gcd(a,b-a,c-b);

我们第一个需要求a是多少,那就是1-l的sum;于是线段树需要维护一个区间sum信息

第二个就是去维护一个区间的gcd信息,去将gcd(b-a,c-b)求出来

void pushup(Node &u,Node &l,Node &r)
{
    u.sum=l.sum+r.sum;
    u.d=gcd(l.d,r.d);
}

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e+5;
typedef long long ll;
ll a[N];
struct Node
{
    int l,r;
    ll sum,d;
}tre[N<<2];
ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}
void pushup(Node &u,Node &l,Node &r)
{
    u.sum=l.sum+r.sum;
    u.d=gcd(l.d,r.d);
}
void pushup(int rt)
{
    pushup(tre[rt],tre[rt*2],tre[rt*2+1]);
}
void build(int rt,int l,int r)
{
    if(l==r)
    {
        tre[rt]={l,r,a[l]-a[l-1],a[l]-a[l-1]};
        
    }
    else
    {
        tre[rt]={l,r};
        int mid=(l+r)/2;
        build(rt*2,l,mid);
        build(rt*2+1,mid+1,r);
        pushup(rt);
    }
}
void modify(int rt,int x,ll d)
{
    if(tre[rt].l==x&&tre[rt].r==x)
    {
        ll b=tre[rt].sum+d;
        tre[rt]={x,x,b,b};
    }
    else
    {
        int mid = (tre[rt].l+tre[rt].r)/2;
        if( x <= mid)
        modify(rt*2,x,d);
        else
        modify(rt*2+1,x,d);
        pushup(rt);
    }
}
Node query(int rt,int l,int r)
{
    if(tre[rt].l>=l&&tre[rt].r<=r)
    {
        return tre[rt];
    }
    else
    {
        int mid=(tre[rt].l+tre[rt].r)/2;
        if( r <= mid)
        {
            return  query(rt*2,l,r);
        }
        else if(l > mid)
        {
            return query(rt*2+1,l,r);
        }
        else
        {
            auto left=query(rt*2,l,r);
            auto right=query(rt*2+1,l,r);
            Node res;      
            pushup(res,left,right);
            return res;
        }
    }
}
int main()
{
    int n,m;
    cin >> n >> m;
    for(int i=1;i<=n;i++)
    cin>>a[i];
    build(1,1,n);
    
    while(m --)
    {
        char op[2];
        cin>>op;
        if(*op=='Q')
        {   int l,r;
            cin >> l >> r;
            Node res={0,0,0,0};
            Node ans = query(1,1,l);
            if(l+1<=r)
            res = query(1,l+1,r);
            ll se=gcd(ans.sum,res.d);
            cout<<abs(se)<<endl;
        }
        else
        {
            int l,r;
            ll d;
            cin >> l >> r >> d;
            modify(1,l,d);
            if(r+1<=n)
            modify(1,r+1,-d);
        }
    }
    
    
}

贴广告 HDU - 2795

思路

单点修改+区间查询,将1-h区间以初始权值w建树(需要注意的是如果h>n,则很多浪费,只需要 建1-n即可,如果不考虑直接以1-h建树会re的),维护一个区间能贴广告的最大值,如果tre[1].v < x,则输出-1,否则 优先选左儿子,不行就选右儿子

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#include<queue>
#include<vector>
#define endl '\n'
#define ll long long
#define PII pair<int,int>
using namespace std;
void IOS() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
}
const int N=2e5+5;
struct Node {
	int l,r;
	int v;
} tre[N<<2];
void pushup(int rt) {
	tre[rt].v=max(tre[rt*2].v,tre[rt*2+1].v);
}
void build(int rt,int l,int r,int w) {
	if(l == r) {
		tre[rt] = {l,r,w};
	} else {
		tre[rt] = {l,r};
		int mid = (tre[rt].l+tre[rt].r)/2;
		build(rt*2,l,mid,w);
		build(rt*2+1,mid+1,r,w);
		pushup(rt);
	}
}
void query(int rt,int d,int l,int r) {
	if(tre[rt].l==tre[rt].r) {

		tre[rt].v -= d;
		printf("%d\n",tre[rt].l);
		return;
	}

	int mid = (l+r)/2;
	if(tre[rt*2].v>=d) {
		query(rt*2,d,l,mid);
	} else
		query(rt*2+1,d,mid+1,r);

	pushup(rt);
}

int main() {
	int h,w,n;
	while(~scanf("%d%d%d",&h,&w,&n)) {
		if(h > n) h = n;
		memset(tre,0,sizeof(tre));
		build(1,1,h,w);

		for(int i=1; i<=n; i++) {
			int d;
			scanf("%d",&d);
			if(tre[1].v<d) {
				printf("-1\n");
				continue;
			} else {
				query(1,d,1,h);
			}
		}
	}
}

posted @ 2021-11-11 17:03  Xiaomostream  阅读(39)  评论(0编辑  收藏  举报