线段树

线段树

区间的性质是可以进行分解的,
大的区间的性质可以由小的区间的性质进行转换

最大数

建树(build) 利用最大长度进行建树,利用数组或者结构体的编号去建树,标明左右节点的编号

访问(query) 利用递归去访问以该节点为根节点的子树

修改(modify) 递归继续修改,从底向上,当每一次进行完递归操作后进行一次pushup操作更新该点的值

pushup 用该节点的 子节点 去更新该节点的值

( a << 1) + 1 == a << 1 | 1
a << 1 + 1 != a << 1 | 1

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 200010;

struct node{
    int l, r;
    int v;
}tr[4 * N];

void pushup (int u){
    tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}

void build (int u, int l, int r){
    if(l == r)
        tr[u] = {l, r};
    else{
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
    return ;
}

int query (int u, int l, int r){
    if(tr[u].l == l && tr[u].r == r) return tr[u].v;//?
    
    int all = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)
        all = query(u << 1, l, mid);
    if(r >= (mid + 1))
        all = max(all, query(u << 1 | 1, mid + 1, r));
    return all;
}

void modify (int u, int x, int v){
    if(tr[u].l == x && tr[u].r == x){
        tr[u].v = v;
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(x <= mid) modify(u << 1, x, v);
    else modify(u << 1 | 1, x, v);
    pushup(u);
}

int main()
{   int m, p;
    int n = 0, last = 0;
    scanf("%d%d", &m, &p);
    build(1, 1, m);

    int x;
    char op[2];
    while (m -- )
    {
        scanf("%s%d", op, &x);
        if (*op == 'Q')
        {
            last = query (1, n - x + 1, n);
            printf("%d\n", last);
        }
        else
        {
            modify(1, ++ n, (last + x) % p);
        }
    }

    return 0;
}

这样的区间查询就不对

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 200010;

struct node{
    int l, r;
    int v;
}tr[4 * N];

void pushup (int u){
    tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}

void build (int u, int l, int r){
    if(l == r)
        tr[u] = {l, r};
    else{
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
    return ;
}

int query (int u, int l, int r){
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;//?
    int all = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)
        all = query(u << 1, l, r);
    if(r >= (mid + 1))
        all = max(all, query(u << 1 | 1, l, r));
    return all;
}

void modify (int u, int x, int v){
    if(tr[u].l == x && tr[u].r == x){
        tr[u].v = v;
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(x <= mid) modify(u << 1, x, v);
    else modify(u << 1 | 1, x, v);
    pushup(u);
}

int main()
{   int m, p;
    int n = 0, last = 0;
    scanf("%d%d", &m, &p);
    build(1, 1, m);

    int x;
    char op[2];
    while (m -- )
    {
        scanf("%s%d", op, &x);
        if (*op == 'Q')
        {
            last = query(1, n - x + 1, n);
            printf("%d\n", last);
        }
        else
        {
            modify(1, n + 1, (last + x) % p);
            n ++ ;
        }
    }

    return 0;
}

这样就对了,不知道为啥

维护最大值

其实想清楚怎么维护就行了,其他的就是线段树的板子、

感觉线段树有点像动态规划的感觉,就是维护的过程和访问的过程,

树中每个节点和起直接相连的节点之间都有固定的一些关系

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 5e5 + 500;

int n, m;
int w[N];

struct node{
    int l, r;
    int lmax, rmax, all, xmax;
}tr[4 * N];

void pushup(node &u, node &l, node &r){//重载,这样就可以用于代替
    u.all = l.all + r.all;
    u.lmax = max(l.lmax, l.all + r.lmax);
    u.rmax = max(r.rmax, l.rmax + r.all);
    u.xmax = max(max(l.xmax, r.xmax), l.rmax + r.lmax);
}

void pushup(int u){
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r){
    if(l == r){
        tr[u] = {l, r, w[r], w[r], w[r], w[r]};
    }
    else{
        tr[u] = {l, r};
        int mid = (l + r ) >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);//得进行回溯,去更新根节点的最大值
    }
}

void modify(int u, int x, int v){
    if(tr[u].l == x && tr[u].r == x){
        tr[u] = {x, x, v, v, v, v};
        return;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    if(x <= mid) modify(u << 1, x, v);
    else modify(u << 1 | 1, x, v);
    pushup(u);
}

node query(int u, int l, int r){
    if(l <= tr[u].l && r >= tr[u].r) return tr[u];
    
    int mid = ( tr[u].l + tr[u].r ) >> 1;
    if(r <= mid) return query(u << 1, l, r);
    else if(l >= (mid + 1)) return query(u << 1 | 1, l, r);
    else{
        auto nol = query(u << 1, l, r);
        auto nor = query(u << 1 | 1, l, r);
        node no ;
        pushup(no, nol, nor);
        return no;
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    int k, x, y;
    while (m -- )
    {
        scanf("%d%d%d", &k, &x, &y);
        if (k == 1)
        {
            if (x > y) swap(x, y);
            printf("%d\n", query(1, x, y).xmax);
        }
        else modify(1, x, y);
    }

    return 0;
}

最大公约数
维护最大公约数,其实感觉是一个数学题,
a1, a2, a3, a4 之间的互相的最大公约数 = a1, (a2 - a1), (a3 - a2), (a4 - a3)之间互相的最大公约数

维护一个差分数组,区间加建就可以变成 O(1) 的复杂度

代码

以上都是只是 pushup 的,没有 pushdown 操作
pushdown + 区间查询板子

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;

const int N = 1e5 + 100;

struct node{
    int l, r;
    LL sum, add;
}tr[4 * N];
LL w[N];

void pushup(int u){
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u){
    node &root = tr[u];
    node &left = tr[u << 1];
    node &right = tr[u << 1 | 1];
    
    if(root.add){//叶子节点
        left.add += root.add;
        left.sum += (LL)(left.r - left.l + 1) * root.add;
        
        right.add += root.add;
        right.sum += (LL)(right.r - right.l + 1) * root.add;
        
        root.add = 0;
    }
}


void build(int u, int l, int r){
    if(l == r) {
        tr[u] = {l, r, w[l], 0};
        return ;
    }
    tr[u] = {l, r};//别忘了左右节点的增加
    int mid = l + r >> 1;
    build(u << 1, l, mid);//直接建边就行了
    build(u << 1 | 1, mid + 1, r);
    pushup(u);//在这里更新结点的值
}


LL query(int u, int l, int r){
    if(l <= tr[u].l && tr[u].r <= r)
        return tr[u].sum;
    
    pushdown(u);//先传递add值,更新子节点的sum值
    int mid = tr[u].l + tr[u].r >> 1;
    
    LL sum = 0;//long long
    if(l <= mid) sum += query(u << 1, l, r);
    if((mid + 1) <= r) sum += query(u << 1 | 1, l, r);
    
    return sum;
}

 void modify(int u, int l, int r, int d){//区间更新,不是一个节点,而是一段
     if(l <= tr[u].l  && tr[u].r <= r){
        tr[u].add += d;
        tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
        return ;
     }
     
     pushdown(u);//先传递add值,更新子节点的sum值
     int mid = tr[u].l + tr[u].r >> 1;
     if(l <= mid) modify(u << 1, l, r, d);
     if(mid < r) modify(u << 1 | 1, l, r, d);
     pushup(u);//修改完子区间后修改该区间的值
 }
 
 int main(){
     int n, m;
     scanf("%d%d",&n,&m);
     for(int i = 1;i <= n;i ++) scanf("%lld",&w[i]);
 
     build(1, 1, n);
     
     while(m --){
         char ch[2];
         int a, b;
         scanf("%s%d%d",ch, &a, &b);
         if(*ch == 'Q') printf("%lld\n",query(1, a, b));//注意是long long
         else {
             int d;
             scanf("%d",&d);
             modify(1, a, b, d);
         }
     }
     return 0;
 }

求矩形面积的和
操作一将区间[l, j] + k
操作而查询整个区间内长度大于0的区间总长是多少

线段树中节点信息,
1, cnt, 当前区间整个被覆盖的次数
2, len不考虑祖先节点cnt的前提下从cnt > 0 的区间总长

永远只考虑根节点的信息, 不会有pushdown的操作
所有操作均是成对出现,且先加后减

用x轴建线段树,
把所有的坐标先全存进去,矩形的左边设权值为1,右边的权值设为-1,这样进行矩形的加减,

为了更好的建树,把x坐标进行离散化,把他们变成连续的整数,这样建树的时候左右的节点的值和普通线段树的分布是一样的

这个代码不用pushdown是因为不用向下进行传值,每次进行计算矩形面积的时候用的只是根节点的cnt值,
因为所有的边都是对称的,添加后必然进行删除操作,而且添加和删除的时候的线段树访问的节点都是一样的,
所以并不用pushdown操作

压缩

#include <cstdio>//照着ac代码敲的,并不是自己写的
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 100010;

int n;

struct Segment{//记录边
    double x, y1, y2;
    int k;
    bool operator < (const Segment &t)const{
        return x < t.x;
    }
}seg[N * 2];

struct Node{//线段树
    int l, r;
    int cnt;
    double len;
}tr[N * 8];

vector <double> ys;//线段树中节点,离散化

int find(double y){
    return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}

void pushup(int u){
    if(tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];//该区间呗完全包含,计算长度为该区间的长度
    else if(tr[u].l != tr[u].r){
        tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    }
    else tr[u].len = 0;
}

void build(int u, int l, int r){
    tr[u] = {l, r, 0, 0};
    if(l != r){
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    }
}

void modify(int u, int l, int r, int k){
    if(tr[u].l >= l && tr[u].r <= r){
        tr[u].cnt += k;
        pushup(u);
    }
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid ) modify (u << 1, l, r, k);
        if(r > mid) modify (u << 1 | 1, l, r, k);
        pushup(u);
    }
}

int main(){
    int T = 1;
    while(scanf("%d",&n), n){
        ys.clear();
        for(int i = 0, j = 0;i < n;i ++){
            double x1, x2, y1, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            seg[j ++] = {x1, y1, y2, 1};
            seg[j ++] = {x2, y1, y2, -1};
            ys.push_back(y1), ys.push_back(y2);
        }
        
        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());
        
        
        build(1, 0, ys.size() - 2);//减2是因为首先从零开始,然后因为是区间,而不是点坐标
        
        sort(seg, seg + n * 2);
        
        double res = 0;
        for(int i = 0;i < n * 2;i ++){
            if(i > 0) res += tr[1].len * (seg[i].x - seg[i-1].x);
            modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
        }
        
        printf("Test case #%d\n", T ++ );
        printf("Total explored area: %.2lf\n\n", res);
    }
    return 0;
}

以上是区间的加法操作,还有区间的乘法操作,
区间乘法

得想明白加和乘的先后顺序和操作的方法

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, p, m;
int w[N];
struct Node
{
    int l, r;
    int sum, add, mul;
}tr[N * 4];

void pushup(int u)
{
    tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}

void eval(Node &t, int add, int mul)
{
    t.sum = ((LL)t.sum * mul + (LL)(t.r - t.l + 1) * add) % p;
    t.mul = (LL)t.mul * mul % p;
    t.add = ((LL)t.add * mul + add) % p;
}

void pushdown(int u)
{
    eval(tr[u << 1], tr[u].add, tr[u].mul);
    eval(tr[u << 1 | 1], tr[u].add, tr[u].mul);
    tr[u].add = 0, tr[u].mul = 1;
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r], 0, 1};
    else
    {
        tr[u] = {l, r, 0, 0, 1};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int l, int r, int add, int mul)
{
    if (tr[u].l >= l && tr[u].r <= r) eval(tr[u], add, mul);
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, add, mul);
        if (r > mid) modify(u << 1 | 1, l, r, add, mul);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum = (sum + query(u << 1 | 1, l, r)) % p;
    return sum;
}

int main()
{
    scanf("%d%d", &n, &p);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    scanf("%d", &m);
    while (m -- )
    {
        int t, l, r, d;
        scanf("%d%d%d", &t, &l, &r);
        if (t == 1)
        {
            scanf("%d", &d);
            modify(1, l, r, 0, d);
        }
        else if (t == 2)
        {
            scanf("%d", &d);
            modify(1, l, r, d, 1);
        }
        else printf("%d\n", query(1, l, r));
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值