一维线段树解析 (HDU 1166)解题报告

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化

让空间压缩。

在ACM题目中,常用线段树解决不断改变的某段区间的和或最大,最小值等问题。 下面我们以杭电1166题为例子讲解线段树的应用。

敌兵布阵

                                                                      Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768
K (Java/Others)

Total Submission(s): 60090    Accepted Submission(s): 25432

Problem Description

C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。

中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.

Input

第一行一个整数T,表示有T组数据。

每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。

接下来每行有一条命令,命令有4种形式:

(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)

(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);

(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;

(4)End 表示结束,这条命令在每组数据最后出现;

每组数据最多有40000条命令

Output

对第i组数据,首先输出“Case i:”和回车,

对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

Sample Input

1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End 

Sample Output

Case 1:
6
33
59

线段树通常由三个函数组成,分别是buildtree(建树),query(查询),update(更新)

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int MM=50000;//10^6
int num[MM<<2];

void buildtree(int l,int r,int id)   //建立编号为l-r的结点 结点编号为id 
{
    if(l==r)
    {
        scanf("%d",&num[id]);return;
    }
    else
    {
        int mid=(l+r)>>1;    //右移  除以(2^1) 
        buildtree(l,mid,id<<1);
        buildtree(mid+1,r,id<<1|1);  //乘以2 加1
	   
    }num[id]=num[id<<1]+num[id<<1|1];//父节点的值为两个子节点的和 
}

int query(int L,int R,int l,int r,int id)  //查询   L-R为查询的区间 
{
    if(L<=l&&R>=r) return num[id];       //找到该区间 
    
    else
    {
        int mid=(l+r)>>1; int res=0;
        
        if(L<=mid) res+=query(L,R,l,mid,id<<1);
        
        if(R>mid) res+=query(L,R,mid+1,r,id<<1|1);
        
        return res;
    }
    
}

void update(int pos,int e,int l,int r,int id)  //更新    pos代表位置信息 e则是改变的值  l-r则是代表此时更新的区间 id则是这个区间所在的结点号 
{
    if(l==r)   //找到那个位置 
    {
        num[id]+=e;return;
    }
    else
    {
        int mid=(l+r)>>1;
        
        if(pos<=mid)update(pos,e,l,mid,id<<1);   //在左结点这边 
        
        else if(pos>mid)update(pos,e,mid+1,r,id<<1|1);  //在右结点这边 
        
        num[id]=num[id<<1]+num[id<<1|1];
    }
}

int main()
{
    int t,n,cas,i,x,y;
    char str[10];
    scanf("%d",&t);
    for(cas=1;cas<=t;cas++)
    {
        printf("Case %d:\n",cas);
        scanf("%d",&n);
        buildtree(1,n,1);
        
        memset(str,0,sizeof str);
        
        while(scanf("%s",str))
        {
       
            if(strcmp(str,"End")==0)break;
            else if(strcmp(str,"Add")==0)
            {
                scanf("%d %d",&x,&y);
                update(x,y,1,n,1);
            }
            
            else if(strcmp(str,"Sub")==0)
            {
                scanf("%d %d",&x,&y);
                update(x,-y,1,n,1);
            }
            
            else
            {
                scanf("%d %d",&x,&y);
                printf("%d\n",query(x,y,1,n,1) );
            }
            
        }//while
        
    }//for()
    
    return 0;
}

这里有个注意点:
理论上线段树消耗的空间为2n-1,但是你递归建立的时候当前节点为r,那么左右孩子分别是2*r,2*r+1,此时编译器并不知道递归已结束,因为你的结束条件是在递归之前的,所以编译器会认为下标访问出错,也就是空间开小了,应该再开大2倍。有时候可能你发现开2,3倍的空间也可以AC,那只是因为测试数据并没有那么大。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-07 19:41:20

一维线段树解析 (HDU 1166)解题报告的相关文章

【线段树】hdu 1166 敌兵布阵

[线段树]hdu 1166 敌兵布阵 题目链接:hdu 1166 敌兵布阵 题目大意 C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况.由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视. 中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时

ACM: Copying Data 线段树-成段更新-解题报告

Copying Data Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I64d & %I64u Description We often have to copy large volumes of information. Such operation can take up many computer resources. Therefore, in this problem you are advised to come

hdu 2112 HDU Today 解题报告

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2112 题目意思:又是求最短路的,不过结合埋字符串来考查. 受之前1004 Let the Balloon Rise 学到用map 的解法做之后,有点蠢蠢欲动,当时见到要用字典树做有点吓坏了(之前看过下,非一般难理解,所以暂时放下了).于是,死就死吧,硬住头皮用map做,反反复复修改终于过了. 首先是memory limit exceeded,因为无读清题目意思,直接开10000 * 10000的数组

【线段树】 HDU 5025 A Corrupt Mayor&#39;s Performance Art

更新区间内颜色 输出区间内的颜色总数 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <string> #include <iostream> #include <algorithm> #include <sstream> #include <math.h> using namespace std; #include <

【线段树】hdu 1754 I Hate It

[线段树]hdu 1754 I Hate It 题目链接:hdu 1754 I Hate It 题目大意 N个学生的初始成绩已知,操作m次,每次要么将第i个学生的成绩更新,要么查找区间[x,y]的最大成绩. 很显然这是一道线段树,点修改.区间查询,笔者第三道线段树,完全自己敲的,直接AC~(≧▽≦)/~啦啦啦. 如果单纯查找区间最大值,时间复杂度O(N),而线段树O(logN),当查询的次数非常多时,显然后者更高效! 说一下思路 线段树的点修改[点更新]:直接将叶节点更新为输入的数据,对照"敌兵

【线段树】HDU 3397 Sequence operation 区间合并

操作 Change operations: 0 a b change all characters into '0's in [a , b] 1 a b change all characters into '1's in [a , b] 2 a b change all '0's into '1's and change all '1's into '0's in [a, b] Output operations: 3 a b output the number of '1's in [a,

一维线段树

下面是一维线段树的例子,它是建立了一棵树,叶子上的value等于在数组中下标为叶子左右节点的值. 这个题目是要求输入一个数字序列,然后输入一个区间,求出区间内的值的和. 1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 const int N=1000; 5 int result; 6 struct Node 7 { 8 int left,right,value; 9 }; 10 Node node[

【线段树】hdu 1556 Color the ball

[线段树]hdu 1556 Color the ball 题目链接:hdu 1556 Color the ball 题目大意 给你N个气球,不断刷新指定区间的颜色,刷新N次,最后输出每一个气球的刷新次数. 上一篇文章是线段树的点修改.区间查询: 这篇文章是线段树的区间修改.点查询: 说一下思路 线段树的区间修改:利用线段树的区间查询,查询到叶节点segTree[root].sum++,而如果对区间进行多次点修改的话,注定超时 线段树的点查询:以为用之前的区间查询就可以了,实际上还是有改变,因为原

【线段树】HDU 1394 Minimum Inversion Number

minimum inversion number:最小逆序数 Minimum Inversion NumberTime Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 9367    Accepted Submission(s): 5754 Problem Description The inversion number of a given nu