线段树(例题)

线段树tq(巨佬太强了)l
题目链接
1、hdu1166敌兵布阵
最基础的线段树应用,这里没有用上区间修改,但是这个题可以当个板子

/*递归方法*/
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>

using namespace std;

const int maxn = 50000 + 5;//元素总个数

int p[4 * maxn],add[4 * maxn];//p各节点信息,add惰性标记
int A[maxn],n;//存储原数组下标对应信息

/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/

//自下向上对节点的更新
void PushUp(int rt)
{
    p[rt] = p[2 * rt] + p[2 * rt + 1];
}

void PushDown(int rt,int ln,int rn)
{
    //ln,rn为左子树,右子树的数字数量
    if(add[rt]){//是否修改过
        //下推标记
        add[2 * rt] += add[rt];
        add[2 * rt + 1] += add[rt];//表示两个字节点也被修改
        p[2 * rt] += add[rt] * ln;
        p[2 * rt + 1] += add[rt] * rn;
        //消除当前节点标记,表示已经下推给下一层节点
        add[rt] = 0;
    }
}

//创建树
void Build(int l,int r,int rt)
{
    //如果抵达叶节点
    if(l == r){
        p[rt] = A[l];
        return ;
    }
    int m = (l + r) / 2;
    //左右递归
    Build(l,m,2 * rt);
    Build(m + 1,r,2 * rt + 1);
    //更新当层节点信息
    PushUp(rt);
}

//点修改
void Point_Update(int L,int c,int l,int r,int rt)//L 要修改的点
{
    //找到要修改的叶节点
    if(l == r){
        p[rt] += c;
        return ;
    }
    int m = (l + r) / 2;
    //判断节点所在哪个区间,同二分查找
    if(L <= m){
        Point_Update(L,c,l,m,2 * rt);
    }
    else{
        Point_Update(L,c,m + 1,r,2 * rt + 1);
    }
    //子节点更新了,本节点也要进行更新
    PushUp(rt);
}

//区间修改
void Line_Update(int L,int R,int c,int l,int r,int rt)//L R表示要操作区间
{
    //如果当前区间完全在要进行修改的区间内
    if(L <= l&&r <= R){
        //更新数字和,向上保持正确
        p[rt] += c * (r - l + 1);
        //增加标记表示本区间已经进行修改,并根据当前add值修改下一层子节点区间
        add[rt] += c;
        return ;
    }
    int m = (l + r) / 2;
    PushDown(rt,m - l + 1,r - m);//下推add标记,若下一层标记需要根据标记修改则修改
    //这里判断左右子树跟[L,R]有无交集,有交集才递归
    if(L <= m){
        Line_Update(L,R,c,l,m,2 * rt);
    }
    if(R > m){
        Line_Update(L,R,c,m + 1,r,2 * rt + 1);
    }
    //更新本节点信息
    PushUp(rt);
}

//区间查询函数
int Query(int L,int R,int l,int r,int rt)
{
    //当前区间在完全在要查询区间内,那么直接返回该区间值
    if(L <= l&&r <= R){
        return p[rt];
    }
    int m = (l + r) / 2;
    //下推标记,不然返回的可能是修改之前的值
    PushDown(rt,m - l + 1,r - m);
    //累计答案
    int ans = 0;
    if(L <= m){
        ans += Query(L,R,l,m,2 * rt);
    }
    if(R > m){
        ans += Query(L,R,m + 1,r,2 * rt + 1);
    }
    return ans;
}

int main()
{
    int t,count = 0;
    cin>>t;
    while(t--){
        count++;
        printf("Case %d:\n",count);
        memset(A,0,sizeof(A));
        memset(p,0,sizeof(p));
        memset(add,0,sizeof(add));
        scanf("%d",&n);
        for(int i = 1; i <=n; i++){
            scanf("%d",&A[i]);
        }
        int Q_ans;
        char oper[15];
        Build(1,n,1);
        while(scanf("%s",oper)&&strcmp(oper,"End")){
            if(!strcmp(oper,"Query")){
                int l,r;
                scanf("%d%d",&l,&r);
                Q_ans = Query(l,r,1,n,1);
                printf("%d\n",Q_ans);
            }
            if(!strcmp(oper,"Add")){
                int L,c;
                scanf("%d%d",&L,&c);
                Point_Update(L,c,1,n,1);
            }
            if(!strcmp(oper,"Sub")){
                int L,c;
                scanf("%d%d",&L,&c);
                c = -c;
                Point_Update(L,c,1,n,1);
            }
        }
    }
    return 0;
}

2、hdu1754 I Hate it
也是模板题,就是把加法换成取最大就彳亍

/*递归方法*/
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>

using namespace std;

const int maxn = 200005;//元素总个数

int p[4 * maxn],add[4 * maxn];//p各节点信息,add惰性标记
int A[maxn],n;//存储原数组下标对应信息

/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/

//自下向上对节点的更新
void PushUp(int rt)
{
    p[rt] = max(p[2 * rt],p[2 * rt + 1]);
}

void PushDown(int rt,int ln,int rn)
{
    //ln,rn为左子树,右子树的数字数量
    if(add[rt]){//是否修改过
        //下推标记
        add[2 * rt] += add[rt];
        add[2 * rt + 1] += add[rt];//表示两个字节点也被修改
        p[2 * rt] += add[rt] * ln;
        p[2 * rt + 1] += add[rt] * rn;
        //消除当前节点标记,表示已经下推给下一层节点
        add[rt] = 0;
    }
}

//创建树
void Build(int l,int r,int rt)
{
    //如果抵达叶节点
    if(l == r){
        p[rt] = A[l];
        return ;
    }
    int m = (l + r) / 2;
    //左右递归
    Build(l,m,2 * rt);
    Build(m + 1,r,2 * rt + 1);
    //更新当层节点信息
    PushUp(rt);
}

//点修改
void Point_Update(int L,int c,int l,int r,int rt)//L 要修改的点
{
    //找到要修改的叶节点
    if(l == r){
        p[rt] = c;
        return ;
    }
    int m = (l + r) / 2;
    //判断节点所在哪个区间,同二分查找
    if(L <= m){
        Point_Update(L,c,l,m,2 * rt);
    }
    else{
        Point_Update(L,c,m + 1,r,2 * rt + 1);
    }
    //子节点更新了,本节点也要进行更新
    PushUp(rt);
}

//区间修改,这里不需要,因为改成绩这里是对应点进行修改
void Line_Update(int L,int R,int c,int l,int r,int rt)//L R表示要操作区间
{
    //如果当前区间完全在要进行修改的区间内
    if(L <= l&&r <= R){
        //更新数字和,向上保持正确
        p[rt] += c * (r - l + 1);
        //增加标记表示本区间已经进行修改,并根据当前add值修改下一层子节点区间
        add[rt] += c;
        return ;
    }
    int m = (l + r) / 2;
    PushDown(rt,m - l + 1,r - m);//下推add标记,若下一层标记需要根据标记修改则修改
    //这里判断左右子树跟[L,R]有无交集,有交集才递归
    if(L <= m){
        Line_Update(L,R,c,l,m,2 * rt);
    }
    if(R > m){
        Line_Update(L,R,c,m + 1,r,2 * rt + 1);
    }
    //更新本节点信息
    PushUp(rt);
}

//区间查询函数
int Query(int L,int R,int l,int r,int rt)
{
    //当前区间在完全在要查询区间内,那么直接返回该区间值
    if(L <= l&&r <= R){
        return p[rt];
    }
    int m = (l + r) / 2;
    int ans = 0;
    if(L <= m){
        ans = max(ans,Query(L,R,l,m,2 * rt));
    }
    if(R > m){
        ans = max(ans,Query(L,R,m + 1,r,2 * rt + 1));
    }
    return ans;
}

int main()
{
    int m,n;
    while(scanf("%d%d",&n,&m) == 2){
        for(int i = 1; i <= n; i++){
            scanf("%d",&A[i]);
        }
        Build(1,n,1);
        int a,b;
        char str[2];
        while(m--){
            scanf("%s%d%d",str,&a,&b);
            if(str[0] == 'Q'){
                int ans = Query(a,b,1,n,1);
                printf("%d\n",ans);
            }
            else{
                Point_Update(a,b,1,n,1);
            }
        }
    }
    return 0;
}

3、poj3264 Balanced Lineup
这个也很简单,但是需要剪枝
这个是tle代码,就是没有剪枝的

#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>

using namespace std;

const int maxn = 50000 + 5;//元素总个数
const int inf = 1e9;

struct point_cow
{
    int minx;
    int maxx;
};
point_cow p[4 * maxn];//p各节点信息
int A[maxn];//存储原数组下标对应信息

/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/

//自下向上对节点的更新
void PushUp(int rt)
{
    p[rt].maxx = max(p[2 * rt].maxx,p[2 * rt + 1].maxx);
    p[rt].minx = min(p[2 * rt].minx,p[2 * rt + 1].minx);
}

//创建树
void Build(int l,int r,int rt)
{
    //如果抵达叶节点
    if(l == r){
        p[rt].maxx = p[rt].minx = A[l];
        return ;
    }
    int m = (l + r) / 2;
    //左右递归
    Build(l,m,2 * rt);
    Build(m + 1,r,2 * rt + 1);
    //更新当层节点信息
    PushUp(rt);
}

//区间查询函数
point_cow Query(int L,int R,int l,int r,int rt)
{
    //当前区间在完全在要查询区间内,那么直接返回该区间值
    if(L <= l&&r <= R){
        return p[rt];
    }
    int m = (l + r) / 2;
    point_cow ans;
    ans.maxx = 0;
    ans.minx = inf;
    if(L <= m){
        ans.maxx = max(ans.maxx,Query(L,R,l,m,2 * rt).maxx);
        ans.minx = min(ans.minx,Query(L,R,l,m,2 * rt).minx);
    }
    if(R > m){
        ans.maxx = max(ans.maxx,Query(L,R,m + 1,r,2 * rt + 1).maxx);
        ans.minx = min(ans.minx,Query(L,R,m + 1,r,2 * rt + 1).minx);
    }
    return ans;
}

int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i = 1; i <= n; i++){
        scanf("%d",&A[i]);
    }
    Build(1,n,1);
    //cout<<p[2].maxx<<' '<<p[2].minx<<endl;
    //cout<<p[6].maxx<<' '<<p[6].minx<<endl;
    while(q--){
        int l,r;
        scanf("%d%d",&l,&r);
        point_cow ans = Query(l,r,1,n,1);
        int res = ans.maxx - ans.minx;
        printf("%d\n",res);
    }
    return 0;
}

这个是剪枝之后的

/*递归方法*/
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>

using namespace std;

const int maxn = 50000 + 5;//元素总个数
const int inf = 1e9;

struct point_cow
{
    int minx;
    int maxx;
};
point_cow p[4 * maxn];//p各节点信息
int A[maxn];//存储原数组下标对应信息
int max1,min1;

/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/

//自下向上对节点的更新
void PushUp(int rt)
{
    if(p[2 * rt].maxx > p[2 * rt + 1].maxx){
        p[rt].maxx = p[2 * rt].maxx;
    }
    else{
         p[rt].maxx = p[2 * rt + 1].maxx;
    }
    if(p[2 * rt].minx < p[2 * rt + 1].minx){
        p[rt].minx = p[2 * rt].minx;
    }
    else{
        p[rt].minx = p[2 * rt + 1].minx;
    }
}

//创建树
void Build(int l,int r,int rt)
{
    //如果抵达叶节点
    if(l == r){
        p[rt].maxx = p[rt].minx = A[l];
        return ;
    }
    int m = (l + r) / 2;
    //左右递归
    Build(l,m,2 * rt);
    Build(m + 1,r,2 * rt + 1);
    //更新当层节点信息
    PushUp(rt);
}

//区间查询函数
void Query(int L,int R,int l,int r,int rt)
{
    //剪枝
    if(p[rt].minx >= min1&&p[rt].maxx <= max1){
        return ;
    }
    //当前区间在完全在要查询区间内,那么直接返回该区间值
    if(L <= l&&r <= R){
        min1 = min(p[rt].minx,min1);
        max1 = max(p[rt].maxx,max1);
        return ;
    }
    int m = (l + r) / 2;
    if(L <= m){
        Query(L,R,l,m,2 * rt);
    }
    if(R > m){
        Query(L,R,m + 1,r,2 * rt + 1);
    }
}

int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i = 1; i <= n; i++){
        scanf("%d",&A[i]);
    }
    Build(1,n,1);
    //cout<<p[2].maxx<<' '<<p[2].minx<<endl;
    //cout<<p[6].maxx<<' '<<p[6].minx<<endl;
    while(q--){
        int l,r;
        max1 = 0;
        min1 = inf;
        scanf("%d%d",&l,&r);
        Query(l,r,1,n,1);
        int res = max1 - min1;
        printf("%d\n",res);
    }
    return 0;
}

4、对某区间内所有数进行操作
hdu4027 Can you answer these queries?
也是模板题,但是在于如何对给定区间内的数进行单独操作
类似于单点操作,但是函数的参数由点的序号变成了区间左右序号

#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 10;//元素总个数

ll p[4 * maxn],add[4 * maxn];//p各节点信息,add惰性标记
ll A[maxn];//存储原数组下标对应信息

/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/

//自下向上对节点的更新
void PushUp(int rt)
{
    p[rt] = p[2 * rt] + p[2 * rt + 1];
}

//创建树
void Build(int l,int r,int rt)
{
    //如果抵达叶节点
    if(l == r){
        p[rt] = A[l];
        return ;
    }
    int m = (l + r) / 2;
    //左右递归
    Build(l,m,2 * rt);
    Build(m + 1,r,2 * rt + 1);
    //更新当层节点信息
    PushUp(rt);
}

//将区间内的每个点进行修改
void Point_Update(int L,int R,int l,int r,int rt)
{
    if(l == r){
        p[rt] = sqrt(p[rt]);
        return ;
    }
    //剪枝
    if(L <= l&&r <= R&&p[rt] == r - l + 1){
        return ;
    }
    int m = (l + r) / 2;
    if(L <= m){
        Point_Update(L,R,l,m,2 * rt);
    }
    if(R > m){
        Point_Update(L,R,m + 1,r,2 * rt + 1);
    }
    PushUp(rt);
}

//区间查询函数
ll Query(int L,int R,int l,int r,int rt)
{
    //当前区间在完全在要查询区间内,那么直接返回该区间值
    if(L <= l&&r <= R){
        return p[rt];
    }
    int m = (l + r) / 2;
    //累计答案
    ll ans = 0;
    if(L <= m){
        ans += Query(L,R,l,m,2 * rt);
    }
    if(R > m){
        ans += Query(L,R,m + 1,r,2 * rt + 1);
    }
    return ans;
}

int main()
{
    int n,m,count = 0;
    while(scanf("%d",&n) == 1){
        for(int i = 1; i <= n; i++){
            scanf("%lld",&A[i]);
        }
        Build(1,n,1);
        scanf("%d",&m);
        count++;
        printf("Case #%d:\n",count);
        while(m--){
            int mark,l,r;
            scanf("%d%d%d",&mark,&l,&r);
            //!!!
            if(l > r){
                swap(l,r);
            }
            if(mark){
                ll ans = Query(l,r,1,n,1);
                printf("%lld\n",ans);
            }
            else{
                Point_Update(l,r,1,n,1);
            }
        }
        printf("\n");
    }
    return 0;
}

5、关于连续区间 hdu1540http://acm.hdu.edu.cn/showproblem.php?pid=1540
题意:
区间1-N M次询问D 表示摧毁该村子通向其他的道路,R表示恢复上次摧毁的道路,Q表示询问该村庄和几个连在一起
思路:
利用线段树求区间中被毁村庄的最大最小值 以样例来说 1 2 3 4 5 6 7将3 5 6摧毁后只需求出区间1->4中被摧毁序号最大村庄 2 号和4 -> 7中被摧毁序号最小村庄5号,这样4号连续连续的村庄数量为min - max -1,但是要特判,如果max = min时,输出0,也即是该点村庄正好是被摧毁的。
代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <stack>

using namespace std;

const int maxn = 50015;
const int inf = 1e9;

struct point
{
    int maxx;
    int minx;
}node[4 * maxn];

int n;

void PushUp(int rt)
{
    node[rt].maxx = max(node[2 * rt].maxx,node[2 * rt + 1].maxx);
    node[rt].minx = min(node[2 * rt].minx,node[2 * rt + 1].minx);
}

void Build(int l,int r,int rt)
{
    if(l == r){
        node[rt].maxx = 0;
        node[rt].minx = n + 1;
        return ;
    }
    int m = (l + r) / 2;
    Build(l,m,2 * rt);
    Build(m + 1,r,2 * rt + 1);
    PushUp(rt);
}

void Update(int point,int pmax,int pmin,int l,int r,int rt)
{
    if(l == r){
        node[rt].maxx = pmax;
        node[rt].minx = pmin;
        return ;
    }
    int m = (l + r) / 2;
    if(point <= m){
        Update(point,pmax,pmin,l,m,2 * rt);
    }
    if(point > m){
        Update(point,pmax,pmin,m + 1,r,2 * rt + 1);
    }
    PushUp(rt);
}

int max_Query(int L,int R,int l,int r,int rt)
{
    if(L <= l&&r <= R){
        return node[rt].maxx;
    }
    int m = (l + r) / 2;
    int ans = 0;
    if(L <= m){
        ans = max(ans,max_Query(L,R,l,m,2 * rt));
    }
    if(R > m){
        ans = max(ans,max_Query(L,R,m + 1,r,2 * rt + 1));
    }
    return ans;
}

int min_Query(int L,int R,int l,int r,int rt)
{
    if(L <= l&&r <= R){
        return node[rt].minx;
    }
    int m = (l + r) / 2;
    int ans = inf;
    if(L <= m){
        ans = min(ans,min_Query(L,R,l,m,2 * rt));
    }
    if(R > m){
        ans = min(ans,min_Query(L,R,m + 1,r,2 * rt + 1));
    }
    return ans;
}

int main()
{
    int t;
    while(scanf("%d%d",&n,&t) == 2){
        Build(1,n,1);
        stack <int> sta;
        char oper[2];
        while(t--){
            int num;
            scanf("%s",oper);
            if(oper[0] == 'D'){
                scanf("%d",&num);
                Update(num,num,num,1,n,1);
                sta.push(num);
            }
            if(oper[0] == 'Q'){
                int x,y;
                scanf("%d",&num);
                x = max_Query(1,num,1,n,1);
                y = min_Query(num,n,1,n,1);
                if(x == y){
                    printf("0\n");
                }
                else{
                    printf("%d\n",y - x - 1);
                }
            }
            if(oper[0] == 'R'){
                num = sta.top();
                sta.pop();
                Update(num,0,n + 1,1,n,1);
            }
        }
    }
    return 0;
}

6、多重懒标记
https://blog.csdn.net/CUCUC1/article/details/107350477
7、离散化 + 线段树
https://blog.csdn.net/CUCUC1/article/details/107370653
8、线段树 + 二分
https://blog.csdn.net/CUCUC1/article/details/107378996
9、区间合并
https://blog.csdn.net/CUCUC1/article/details/108538721

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值