线段树练习题(不断更新中)

通过参考大神们线段树的文章,准备开始要一个一个把上面的题目做一遍了,有很多都是原来做过的,现在也再次做一遍方便以后查阅

打过 * 的表示对别人的想法有所参考,留待以后再做一次

现在比起一开始接触线段树已经更为容易理解了,想到自己暑期集训的时候还是傻傻的背着线段树的格式在做题,不肯动脑子去思考

代码含义,觉得自己会背格式就足够了,后来也一直只会套模版题稍微一变就傻了,现在想想当时懒得动脑筋确实是一个笑话

现在想想最简单来看就是把线段树看成一个能够维护一些自己所需数据的二叉树,通过所维护的数据来判断自己是否需要向下维护左子树

和右子树记得回溯即可

HDU 1166 敌兵布阵

单点增减,区间和查询

线段树:

#include <cstdio>
#include <cstring>

using namespace std;
const int N = 50005;
#define ls o<<1
#define rs o<<1|1

int a[N];
char s[10];

struct Tree{
    int l , r , sum;
}tree[N<<2];

void build(int o , int l , int r)
{
    int m = (l + r) >> 1;
    tree[o].l = l , tree[o].r = r;
    if(l == r){
        tree[o].sum = a[l];
        return ;
    }
    build(ls , l , m);
    build(rs , m+1 , r);
    tree[o].sum = tree[ls].sum + tree[rs].sum;
}

void update(int o , int i , int j , int op)
{
    if(tree[o].l == tree[o].r && tree[o].r == i){
        if(op == 1) tree[o].sum += j;
        if(op == 2) tree[o].sum -= j;
        return ;
    }
    int m = (tree[o].l + tree[o].r) >> 1;
    if(m >= i) update(ls , i , j , op);
    if(m+1 <= i) update(rs , i , j , op);
    tree[o].sum = tree[ls].sum + tree[rs].sum;
}

int query(int o , int s , int t)
{
    if(tree[o].l >= s && tree[o].r <= t)
        return tree[o].sum;
    int m = (tree[o].l + tree[o].r) >> 1;
    int ans = 0;
    if(m >= s) ans += query(ls , s , t);
    if(m+1 <= t) ans += query(rs , s , t);
    return ans;
}

int main()
{
   // freopen("a.in" , "r" , stdin);
    int T , cas = 0;
    scanf("%d" , &T);
    while(T--){
        int n , aa , bb;
        scanf("%d" , &n);
        for(int i = 1 ; i<=n ; i++)
            scanf("%d" , &a[i]);
        build(1 , 1 , n);
        printf("Case %d:\n" , ++cas);
        while(scanf("%s" , s))
        {
            if(s[0] == ‘E‘) break;
            scanf("%d%d" , &aa , &bb);
            if(s[0] == ‘A‘) update(1 , aa , bb , 1);
            else if(s[0] == ‘S‘) update(1 , aa , bb , 2);
            else printf("%d\n" , query(1 , aa , bb));
        }
    }
    return 0;
}

堆序列:

#include <cstdio>
#include <cstring>

using namespace std;
const int N = 50005;

int a[N] , sum[N<<2] , k;
char s[10];

void build(int n)
{
    memset(sum , 0 , sizeof(sum));
    k = 1;
    while(k < n+2) k<<=1;
    for(int i = 1 ; i<=n ; i++)
        sum[k+i] = a[i];
    for(int i = k-1 ; i>=1 ; i--)
        sum[i] = sum[i<<1] + sum[i<<1|1];
}

void update(int o)
{
    for(int i = o ; i^1 ; i>>=1)
        sum[i>>1] = sum[i] + sum[i^1];
}

int query(int s , int t)
{
    int i = k+s-1 , j = k+t+1;
    int ans = 0;
    for(; i^j^1 ; i>>=1 , j>>=1){
        if(~i & 1) ans += sum[i^1];
        if(j & 1) ans += sum[j^1];
    }
    return ans;
}

int main()
{
  //  freopen("a.in" , "r" , stdin);
    int T , cas = 0;
    scanf("%d" , &T);
    while(T--){
        int n , aa , bb;
        scanf("%d" , &n);
        for(int i = 1 ; i<=n ; i++)
            scanf("%d" , &a[i]);
        build(n);
        printf("Case %d:\n" , ++cas);
        while(scanf("%s" , s))
        {
            if(s[0] == ‘E‘) break;
            scanf("%d%d" , &aa , &bb);
            if(s[0] == ‘A‘){
                sum[aa+k] += bb;
                update(aa+k);
            }
            else if(s[0] == ‘S‘){
                sum[aa+k] -= bb;
                update(aa+k);
            }
            else printf("%d\n" , query(aa , bb));
        }
    }
    return 0;
}

HDU 1754

老师对学生成绩的更新,找某一区间内学生成绩的最大值

线段树:

#include <cstdio>
#include <cstring>

using namespace std;
#define ls o<<1
#define rs o<<1|1

const int N = 200005;
int score[N] , maxn[N<<2] , ans;
char s[5];

void build(int o , int l , int r)
{
    if(l == r){
        maxn[o] = score[l];
        return ;
    }
    int m = (l+r)>>1;
    build(ls , l , m);
    build(rs , m+1 , r);
    maxn[o] = maxn[ls] > maxn[rs] ? maxn[ls] : maxn[rs];
}

void update(int o , int l , int r , int i , int v)
{
    if(l == r && r == i){
        maxn[o] = v;
        return ;
    }
    int m = (l+r) >> 1;
    if(m >= i) update(ls , l , m , i , v);
    if(m < i) update(rs , m+1 , r , i , v);
    maxn[o] = maxn[ls] > maxn[rs] ? maxn[ls] : maxn[rs];
}

void query(int o , int l , int r , int s , int t)
{
    if(l >= s && r<=t){
        ans = ans > maxn[o] ? ans : maxn[o];
        return;
    }
    int m = (l+r) >> 1;
    if(m >= s) query(ls , l , m , s , t);
    if(m < t) query(rs , m+1 , r , s , t);
}

int main()
{
  //  freopen("a.in" , "r" , stdin);
    int n , m , a , b;
    while(~scanf("%d%d" , &n , &m))
    {
        for(int i = 1;  i<= n ;i++)
            scanf("%d" , score+i);

        build(1 , 1 , n);

        for(int i = 0 ; i<m ; i++)
        {
            scanf("%s%d%d" , s , &a , &b);
            if(s[0] == ‘U‘) update(1 , 1 , n , a , b);
            else{
                ans = 0;
                query(1 , 1 , n , a , b);
                printf("%d\n" , ans);
            }
        }
    }
    return 0;
}

堆序列:

#include <cstdio>
#include <cstring>

using namespace std;

const int N = 200005;

int maxn[N<<2] , D , score[N];
char s[5];

void build(int n)
{
    D = 1;
    memset(maxn , 0 , sizeof(maxn));
    while(D < n+2) D <<= 1;
    for(int i = 1 ; i<=n ; i++)
        maxn[D+i] = score[i];
    for(int i = D-1 ; i>=1 ; i--)
        maxn[i] = maxn[i<<1] > maxn[i<<1|1] ? maxn[i<<1] : maxn[i<<1|1];
}

void update(int o)
{
    for(int i = D + o ; i^1 ; i>>=1)
        maxn[i>>1] = maxn[i] > maxn[i^1] ? maxn[i] : maxn[i^1];
}

int query(int a , int b)
{
    int i = D + a - 1 , j = D + b +1;
    int ans = 0;
    for(; i^j^1 ; i>>=1 , j>>=1)
    {
        if(~i & 1) ans = ans > maxn[i^1] ? ans : maxn[i^1];
        if(j & 1) ans = ans > maxn[j^1] ? ans : maxn[j^1];
    }
    return ans;
}

int main()
{
   // freopen("a.in" , "r" , stdin);
    int n , m , a , b;
    while(~scanf("%d%d" , & n , &m))
    {
        for(int i = 1 ; i<=n ; i++)
            scanf("%d" , score+i);

        build(n);

        for(int i =0 ; i<m ; i++){
            scanf("%s%d%d" , s , &a , &b);
            if(s[0] == ‘U‘)
            {
                maxn[D+a] = b;
                update(a);
            }
            else
                printf("%d\n" , query(a , b));
        }
    }
    return 0;
}

HDU 2795 BillBoard

将海报一张张贴在墙上,总希望贴在最上方,然后往最左侧贴

每次输出海报贴在墙上的位置,如果贴不上去输出-1

这里就是关于建立线段树的小问题

实际上h可以取到10^9是没有必要的,h最大也就是n的最大值200000

具体看代码最上方

/*
因为每张海报最多只能占据一行,否则贴不上去
所以高度h最大取到200000即可,10^9就是吓唬人的
所以维护200000行的剩余宽度,每次取最大值
*/
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 200005;
#define ls o<<1
#define rs o<<1|1

int maxn[N<<2] , left[N] , w , h;

void build(int o , int l , int r , int w)
{
    maxn[o] = w;
    if(l == r) return;
    int m = (l+r) >> 1;
    build(ls , l , m , w);
    build(rs , m+1 , r , w);
}

void update(int o , int l , int r , int i , int v)
{
    int m = (l+r) >> 1 ;
    if(l == r && r == i){
        maxn[o] -= v;
        return;
    }
    if(m >= i) update(ls , l , m , i , v);
    if(m < i) update(rs , m+1 , r , i ,v);
    maxn[o] = max(maxn[ls] , maxn[rs]);
}

int query(int o , int l , int r , int v)
{
    if(maxn[o] < v) return -1;
    if(l == r)
        return l;
    int m = (l+r) >> 1;
    int ans;
    if(maxn[ls] >= v) ans = query(ls , l , m , v);
    else ans = query(rs , m+1 , r , v);
    return ans;
}

int main()
{
   // freopen("a.in" , "r" , stdin);
    int h , w , n;
    while(~scanf("%d%d%d" , &h , &w , &n))
    {
        int t = min(n , h);
        build(1 , 1 , t , w);
        for(int i = 0 ; i<n ; i++)
        {
            int a;
            scanf("%d" , &a);
            int ans = query(1 , 1 , t , a);
            printf("%d\n" , ans);
            if(ans > 0) update(1 , 1 , t , ans , a);
        }
    }
    return 0;
}

POJ 2828 插队问题

一个数插到某个位置上,其后面的数字都要往后推移,最后输出整个序列

用线段树维护一个总空位的值

大概思路看代码上方

/*
这里的数理解为自己前方有多少个
数在自己前面
在线段树上记录对应节点的位置总
共有多少个空位
因为前面的数会受后面的数的影响
但是后面的数肯定不收前面的影响
所以可以先插入后面的数到固定位置
往前不断插入数字,碰到左子树有足够
的空位就往左移,如果不够,右移
所需位置要减去左侧位置
*/
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 200005;
#define ls o<<1
#define rs o<<1|1

int sum[N<<2] , left[N] , id[N] , rec[N] , val[N]; //sum记录这个节点对应的区间还有多少位置

void push_up(int o)
{
    sum[o] = sum[ls] + sum[rs];
}

void build(int o , int l , int r)
{
    if(l == r){
        sum[o] = 1;
        return;
    }
    int m = (l+r) >> 1;
    build(ls , l , m);
    build(rs , m+1 , r);
    push_up(o);
}

void update(int o , int l , int r , int i)
{
    int m = (l+r) >> 1 ;
    if(l == r && r == i){
        sum[o] = 0;
        return;
    }
    if(m >= i) update(ls , l , m , i);
    if(m < i) update(rs , m+1 , r , i);
    push_up(o);
}

int query(int o , int l , int r , int v)
{
    if(l == r)
        return l;
    int m = (l+r) >> 1;
    int ans;
    if(sum[ls] >= v) ans = query(ls , l , m , v);
    else ans = query(rs , m+1 , r , v - sum[ls]);
    return ans;
}

int main()
{
  //  freopen("a.in" , "r" , stdin);
    int n;
    while(~scanf("%d" , &n))
    {
        build(1 , 1 , n);
        for(int i = 1 ; i<= n ; i++){
            scanf("%d%d" , &id[i] , &val[i]);
        }
        for(int i = n ; i>=1 ; i--){
            int tmp = query(1 , 1 , n , id[i]+1);
            rec[tmp] = val[i];
            update(1 , 1 , n , tmp);
        }
        for(int i = 1 ; i<=n ; i++)
            printf("%d " , rec[i]);
        puts("");
    }
    return 0;
}

*POJ 1394

给定一堆序列,来判断所有i>j , a[i] > a[j]的组数 , 另外可以不断把收个元素放在后面 , 这个序列的元素是由0~n-1组成

往往碰到添加 n 个连续序号的题目都可以通过不断将元素添入线段树对应的 i 位置逐个维护

因为是0~n-1所以可以通过一个个添加节点实现,我这里用的是逆向添加节点,每次判断添加元素的前方已经有几个位置放了元素,那么

这么多个位置就可以跟当前添加元素形成的对数

另外每次将首个数放到最后方

减少的是 a[i] - 1 (a[i]从1开始) , 我自己题目为了自己好理解把所有字母 加 了1 , 本来是从0开始 的

增加的是 n - a[i] ,然后不断更新最小值即可

自己一开始对最后的更新n一直找不到线性的方法还是看了别人的思路得到的,自己还要不断进步啊

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

#define ls o<<1
#define rs o<<1|1
const int N = 50010;

int sum[N<<2] , num[N]; //维护线段树上空余节点的总个数

void build(int o , int l , int r)
{
    int m = (l+r) >> 1;
    sum[o] = 0;
    if(l == r) return;
    build(ls , l , m);
    build(rs , m+1 , r);
}

void update(int o , int l , int r , int i)
{
    int m = (l + r) >> 1;
    if(l == r && r == i){
        sum[o] = 1;
        return;
    }
    if(m >= i) update(ls , l , m , i);
    else update(rs , m+1 , r , i);
    sum[o] = sum[o<<1] + sum[o<<1|1];
}

int query(int  o , int l , int r , int i)
{
    int m = (l+r) >> 1;
    if(r < i) return sum[o];
    if(l >= i) return 0;
    int ans = 0;
    if(m >= i) ans += query(ls , l , m , i);
    else ans += query(rs , m+1 , r , i) + sum[ls];
    return ans;
}

int main()
{
  //  freopen("a.in" , "r" , stdin);
    int n;
    while(~scanf("%d" , &n))
    {
        int ans = (1<<30) , tmp = 0;
        build(1 , 1 , n);
        for(int i = 0 ; i<n ; i++)
        {
            scanf("%d" , num+i);
            num[i]++;
        }
        for(int i = n-1 ; i>=0 ; i--){
            tmp += query(1 , 1 , n , num[i]);
            update(1 , 1 , n , num[i]);
        }
        ans = min(ans , tmp);
        for(int i = 0 ; i<n ; i++){
            tmp -= num[i]-1;
           // cout<<"tmp1: "<<tmp<<endl;
            tmp += n-num[i];
           // cout<<"tmp2: "<<tmp<<endl;
            ans = min(ans , tmp);
        }
        printf("%d\n" , ans);
    }
    return 0;
}

时间: 2024-10-29 19:08:46

线段树练习题(不断更新中)的相关文章

Codeforces Round #149 (Div. 2) E. XOR on Segment (线段树成段更新+二进制)

题目链接:http://codeforces.com/problemset/problem/242/E 给你n个数,m个操作,操作1是查询l到r之间的和,操作2是将l到r之间的每个数xor与x. 这题是线段树成段更新,但是不能直接更新,不然只能一个数一个数更新.这样只能把每个数存到一个数组中,长度大概是20吧,然后模拟二进制的位操作.仔细一点就行了. 1 #include <iostream> 2 #include <cstdio> 3 #include <cmath>

Light OJ 1411 Rip Van Winkle`s Code 线段树成段更新

题目来源:Light OJ 1411 Rip Van Winkle`s Code 题意:3中操作 1种查询 求区间和 其中每次可以把一段区间从左到右加上1,2,3,...或者从右到左加上...3,2,1 或者把某个区间的数都置为v 思路:我是加了6个域 add是这段区间每个数都要加上add  add是这么来的 对与123456...这个等差数列 可能要分为2个区间 那么我就分成123和123 两个右边的等差数列每个数还应该加上3 所以右区间add加3 v是这个区间都要置为v 他的优先级最高 b是

CodeForces 52C Circular RMQ(区间循环线段树,区间更新,区间求和)

转载请注明出处:http://blog.csdn.net/u012860063 题目链接:http://codeforces.com/problemset/problem/52/C You are given circular array a0,?a1,?...,?an?-?1. There are two types of operations with it: inc(lf,?rg,?v) - this operation increases each element on the segm

FZU Problem 2171 防守阵地 II (线段树,区间更新)

 Problem 2171 防守阵地 II Accept: 143    Submit: 565Time Limit: 3000 mSec    Memory Limit : 32768 KB  Problem Description 部队中总共有N个士兵,每个士兵有各自的能力指数Xi,在一次演练中,指挥部确定了M个需要防守的地点,指挥部将选择M个士兵依次进入指定地点进行防守任务,获得的参考指数即为M个士兵的能力之和.随着时间的推移,指挥部将下达Q个指令来替换M个进行防守的士兵们,每个参加完防守

HDU 1698 Just a Hook (线段树 成段更新 lazy-tag思想)

题目链接 题意: n个挂钩,q次询问,每个挂钩可能的值为1 2 3,  初始值为1,每次询问 把从x到Y区间内的值改变为z.求最后的总的值. 分析:用val记录这一个区间的值,val == -1表示这个区间值不统一,而且已经向下更新了, val != -1表示这个区间值统一, 更新某个区间的时候只需要把这个区间分为几个区间更新就行了, 也就是只更新到需要更新的区间,不用向下更新每一个一直到底了,在更新的过程中如果遇到之前没有向下更新的, 就需要向下更新了,因为这个区间的值已经不统一了. 其实这就

hdu 1556:Color the ball(线段树,区间更新,经典题)

Color the ball Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 7941    Accepted Submission(s): 4070 Problem Description N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的"小飞鸽"牌电

hdu 1698:Just a Hook(线段树,区间更新)

Just a Hook Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 15129    Accepted Submission(s): 7506 Problem Description In the game of DotA, Pudge's meat hook is actually the most horrible thing f

【POJ】3468 A Simple Problem with Integers ——线段树 成段更新 懒惰标记

A Simple Problem with Integers Time Limit:5000MS   Memory Limit:131072K Case Time Limit:2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each

hdu 1166 敌兵布阵(线段树之 单点更新+区间求和)

敌兵布阵                                                                             Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵

poj 3468 A Simple Problem with Integers (线段树 成段更新 加值 求和)

题目链接 题意: 只有这两种操作 C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000."Q a b" means querying the sum of Aa, Aa+1, ... , Ab. 分析:自己写的有点麻烦了,写的时候手残+脑残,改了好久. val+lazy*(r-l+1)表示和,如果lazy==0表示当前区间加的值不统一. 1 #include <iostream