线段树模板

来源:互联网 发布:电脑桌面提示语软件 编辑:程序博客网 时间:2024/06/06 07:31

原贴:http://blog.csdn.net/aleichen/article/details/52068437


//===========================================

//segment tree
//final version
//by kevin_samuel(fenice)
//本模板为转载模板,后面的注释和主函数的验证为Alei添加
#include <iostream>
#include <cstdio>
#include <cmath>
//线段树


using namespace std;


#define MAXN 100
#define INF 0x3fffffff


int A[MAXN];         //操作的序列,记得为(1...n)非(0...n)
//int max;
//int min;


struct node
{
    int left;
    int right;
    int max;           //维护最大值
    int sum;          //维护区间和
    int min;           //维护最小值
}Tree[MAXN<<2];        //存储线段树




void maintain(int root)         //向上调整,使得让线段树维护区间最小值最大值区间和
{
    int LC = root<<1;       //此根的左孩子
    int RC = (root<<1)+1;       //此根的右孩子
    Tree[root].sum = Tree[LC].sum + Tree[RC].sum;       //根的区间和
    Tree[root].max = max(Tree[LC].max,Tree[RC].max);        //根的最大值
    Tree[root].min = min(Tree[LC].min,Tree[RC].min);        //根的最小值
}


void Build(int root,int start,int end)                     //构建线段树
{                                                           //初始化时传入Build(1,1,n);
    Tree[root].left = start;            //建区间大小
    Tree[root].right = end;
    if(start == end)                    //当到达叶子节点时
    {
        Tree[root].sum = A[start];
        Tree[root].max = A[start];
        Tree[root].min = A[start];
        return;
    }
    int mid = (start + end)>>1;         //中间分开
    Build(root<<1,start,mid);           //对左孩子建树,左边孩子的编号为root*2
    Build((root<<1)+1,mid+1,end);       //对右边孩子建树
    maintain(root);
}


void update(int root,int pos,int value)                     //更新点的值
{
    if(Tree[root].left == Tree[root].right && Tree[root].left == pos)   //更新叶子节点的值
    {
        Tree[root].sum += value;
        Tree[root].max += value;
        Tree[root].min += value;
        return;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;          //中间分开成两个区间
    if(pos <= mid)                                       //更新的值在左孩子
        update(root<<1,pos,value);                          //更新左孩子
    else
        update((root<<1)+1,pos,value);                  //更新的值在右孩子
    maintain(root);                                 //叶子节点更新完成后,会回溯到他的父节点,这样一直往上更新到根节点,维护线段树性质
}


int Query(int root,int start,int end)                         //查询区间和(start, end)根节点为1
{
    if(start == Tree[root].left && Tree[root].right == end)         //正好匹配到查询区间,直接返回区间和
    {
        return Tree[root].sum;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;                 //分开区间
    int ret = 0;
    if(end <= mid)                                              //查询结果在左边区间
        ret += Query(root<<1,start,end);                        //将左区间的查询结果返回,并记录在结果和中
    else if(start >= mid+1)                                    //查询结果在右区间
        ret += Query((root<<1)+1,start,end);
    else                                                        //查询结果包含在左右两个区间中
    {
        ret += Query(root<<1,start,mid);            //查左的一部分
        ret += Query((root<<1)+1,mid+1,end);            //查右的一部分
    }
    return ret;                             //返回本次查询结果
}

/*********************************************************************/

LL RminQ(LL root, LL start, LL end) {
//查询区间和当前节点区间没有交集
if(start > Tree[root].right || end < Tree[root].left) return INF;
//当前节点区间包含在查询区间内
if(start <= Tree[root].left && Tree[root].right <= end) {
return Tree[root].min;
}
//分别从左右子树查询,返回两者查询结果的较小值
return min(RminQ(root<<1, start, end), RminQ((root<<1)+1, start, end));

/***********************************************************************/

int RminQ(int root,int start,int end)              //查询区间最小值
{
    if(start == Tree[root].left && Tree[root].right == end)             //正好匹配区间
    {
        return Tree[root].min;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;                  //区间分开,去查左右孩子
    int ret = INF;                                             //先把结果记录为很大
    if(end <= mid)                                          //  完全左区间匹配
        ret = min(ret,RminQ(root<<1,start,end));
    else if(start >= mid+1)                                 //完全右区间匹配
        ret = min(ret,RminQ((root<<1)+1,start,end));
    else
    {
        int a = RminQ(root<<1,start,mid);
        int b = RminQ((root<<1)+1,mid+1,end);
        ret = min(a,b);                                     //求左右区间和匹配区间相符的最小值的较小值
    }
    return ret;                             //记得要返回本次查询的结果
}


int RmaxQ(int root,int start,int end)                 //查询区间最大值
{
    if(start == Tree[root].left && Tree[root].right == end)
    {
        return Tree[root].max;
    }
    int mid = (Tree[root].left + Tree[root].right)>>1;
    int ret = 0;                                        //************可能是 (-INF)要尽可能的小
    if(end <= mid)
        ret = max(ret,RmaxQ(root<<1,start,end));        //完全左孩子区间匹配
    else if(start >= mid+1)
        ret = max(ret,RmaxQ((root<<1)+1,start,end));        //完全右孩子区间匹配
    else
    {
        int a = RmaxQ(root<<1,start,mid);
        int b = RmaxQ((root<<1)+1,mid+1,end);
        ret = max(a,b);                                 //求的左右两个区间和匹配区间相符的最大值得较大者
    }
    return ret;                             //记得返回结果
}


int main()
{
    for(int i = 1; i <= 10; i++)
        A[i] = i;
    Build(1,1,10);
    cout << " (1..10)的数组,对应值如下:" << endl;
    for(int i = 1; i <= 10; i++)
        cout << A[i] << " ";
    cout << endl;
    cout << "(1..5)区间最小值:" << RminQ(1,1,5) << endl;
    cout << " (1..5)区间最大值:" << RmaxQ(1,1,5) << endl;
    cout << " (1..5)区间和:" << Query(1,1,5) << endl;
    cout << " 把位置1的值加上100: " << endl;
    update(1, 1, 100);
    cout << " (1..5)区间最小值" << RminQ(1,1,5) << endl;
    cout << " (1..5)区间最大值:" << RmaxQ(1,1,5) << endl;
    cout << " (1..5)区间和:" << Query(1,1,5) << endl;
    return 0;
}
/*
*总结一下线段树:
*(1)维护区间和,使得区间求和在O(log(n))的复杂度下完成
*(2)维护区间最小值,使得查询区间最小值在O(log(n))的复杂度下完成
*(3)维护区间最大值
****************************************************************
*(4)更新序列中某个值,也能使得树维护区间的最大,最小,和区间和。
*(5)基于区间和的应用,最小值得应用,例如序列的动态更新查询

*/










/**************************************************************************/

/*hihoCoder 1586
2017 ACM-ICPC 北京 网络赛 -I题 
*/ 
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3fffffff


const int MAXN=2e5;
typedef long long LL;


int arr[MAXN];
struct node {
LL left, right, min, max;//储存区间最小和最大值 
}Tree[MAXN<<2];//线段树需要的空间为数组大小的四倍


void maintain(LL root) {//回溯时,对于每个节点,比较left、right的值的大小,更新 
LL L = root<<1, R = (root<<1)+1;
Tree[root].max = max(Tree[L].max, Tree[R].max); 
Tree[root].min = min(Tree[L].min, Tree[R].min); 
}


void Build(LL root, LL start, LL end) {
//left,right表示该节点记录的区间为[left,right] 
Tree[root].left = start;
Tree[root].right = end;
if(start == end) {//start等于end,区间只有一个元素,节点记录该单元素
Tree[root].max = arr[start];
Tree[root].min = arr[start];
return; 
}
//递归构造左右子树
LL mid = (start+end)>>1;
Build(root<<1, start, mid); 
Build((root<<1)+1, mid+1, end); 
//上方递归完后,此根节点的左右子树已建好,即可确定此节点需保存的区间信息。 
maintain(root);
}


void update(LL root, LL pos, LL value) {
//left == right 表示找到了叶子节点
if(Tree[root].left == Tree[root].right) {
Tree[root].max = value;
Tree[root].min = value;
return;
}
//显然,叶子节点值改变,需要更新所有包含此节点信息的节点 
LL mid = (Tree[root].left + Tree[root].right)>>1;
if(pos <= mid)
update(root<<1, pos, value);
else
update((root<<1)+1, pos, value);
//同理 
maintain(root);
}


LL RminQ(LL root, LL start, LL end) {
//查询区间和当前节点区间没有交集
if(start > Tree[root].right || end < Tree[root].left) return INF;
//当前节点区间包含在查询区间内
if(start <= Tree[root].left && Tree[root].right <= end) {
return Tree[root].min;
}
//分别从左右子树查询,返回两者查询结果的较小值
return min(RminQ(root<<1, start, end), RminQ((root<<1)+1, start, end));



LL RmaxQ(LL root, LL start, LL end) {
if(start > Tree[root].right || Tree[root].left > end) return -INF;
if(start <= Tree[root].left && Tree[root].right <= end) {
        return Tree[root].max;
    }
return max(RmaxQ(root<<1, start, end), RmaxQ((root<<1)+1, start, end));
}
/******************************************************************************/

//区间更新和建立,不同的题目

struct node {
LL l, r, sum, lazy, v;
}tree[MAXN<<2];


void maintain(LL root) {
LL L=root<<1, R=root<<1|1;
tree[root].sum = tree[L].sum + tree[R].sum;
}


void Build(LL root, LL start, LL end) {
tree[root].l = start;
tree[root].r = end;
tree[root].lazy = 0;
tree[root].v = 0;
if(start == end) {
tree[root].sum = 1;
return ;
}
LL mid = (start+end)>>1;
Build(root<<1, start, mid);
Build(root<<1|1, mid+1, end);
maintain(root); 
}


void update(LL root, LL X, LL Y, LL Z) {  //重点是更新, 其他很简单
if(tree[root].l == X && tree[root].r == Y) {    // 如果此时区间的范围与需要查询的区间的范围刚好相等,修改此区间的信息后,

tree[root].v = Z;//不继续向下更新
tree[root].lazy = 1;
tree[root].sum = (Y-X+1)*Z;
return ;
}
LL mid = (tree[root].l+tree[root].r)>>1;
if(tree[root].lazy == 1) {//如果此时遇到了lazy==1的区间(说明此区间下面的分支还没更新过)
tree[root].lazy = 0;//lazy改为0,更新下面的区间(注意:这里只向下更新一级)
update(root<<1, tree[root].l, mid, tree[root].v);//因为左下一级的l,r肯定分别等于 上一级的tree[root].l , mid; 
update(root<<1|1, mid+1, tree[root].r, tree[root].v);
tree[root].v = 0;//v改为0
}
if(mid >= Y) {//如果需要更新的区间在左区间;更新左区间
update(root<<1, X, Y, Z);
}else if(mid < X) {
update(root<<1|1, X, Y, Z);
}else {
update(root<<1, X, mid, Z);//要更新的区间在tree[root].l和tree[root].r之间,分别更新
update(root<<1|1, mid+1, Y, Z);
}
maintain(root);//更新上层
}

/*********************************************************************************/

int main() {
int T;
scanf("%d",&T);
while(T--) {
int n;
scanf("%d",&n);
n = 1 << n;
    for(int i = 1; i <= n; i++) scanf("%lld",&arr[i]);
    Build(1,1,n);
    
    int Q;
long long tmp, bg, ed;
scanf("%d",&Q);
for(int i = 0; i < Q; i++) {
scanf("%lld%lld%lld",&tmp, &bg, &ed);
if(tmp==1) {
    LL minNum = RminQ(1,bg+1,ed+1);
    LL maxNum = RmaxQ(1,bg+1,ed+1);
    printf("%lld\n",min(maxNum*maxNum,min(minNum*minNum,minNum*maxNum)));
}else {
update(1, bg+1, ed);
}
}
}
return 0;

/****************************************************************************************/

原创粉丝点击