线段树相关

1》 单点更新,区间求最值

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1754

题目大意:       有N个数储存为[1, N]    有M次操作      

操作类型:     

  》 Q  a   b 表示[a, b] 内最大值     

  》 C   a  b 表示把a的值改为b

思路:  线段树单点更新  区间求最值。。

代码:这样超时了   证明递归太浪费时间。。。

#include<iostream>
#include<stdio.h>
using namespace std;

#define max(a, b) (a>b?a:b)
#define min(a, b) (a<b?a:b)
struct node
{
    int left, right;
    int maxx;     //保存区间[left, right]的最大值
} stu[200001*4];
int s[200001];
void create(int i, int a, int b)//建树
{
    stu[i].left = a;
    stu[i].right = b;
    if(a == b)
    {
        stu[i].maxx = s[a];
        return ;
    }
    int mid = (a+b)/2;
    create(i*2, a, mid);
    create(i*2+1, mid+1, b);
    stu[i].maxx = max(stu[i*2].maxx, stu[i*2+1].maxx);
}
void  change(int i, int a, int b) //单点更新
{
    if(stu[i].left==a && stu[i].right == a) //这样也可以if(stu[i].left == stu[i].right) 按条件递归下去肯定只能到a点
    {
        stu[i].maxx = b;
        return ;
    }
    int mid = (stu[i].left + stu[i].right) /2;
    if(a <= mid) change(i*2, a, b);
    else       change(i*2+1, a, b);
    stu[i].maxx = max(b, stu[i].maxx); //当前区间最大值取 b 和 原来区间的最大值 中大的
}
int find1(int i, int a, int b)
{
    if(stu[i].left ==a && stu[i].right == b) //找到区间返回最大值
        return stu[i].maxx;
    int mid = (stu[i].left+stu[i].right) / 2;
    if(b<=mid)                              //完全在当前区间的左子区间
        return find1(i*2, a, b);
    else if(a>mid)                          //完全在当前区间的右子区间
        return find1(i*2+1, a, b);
    else                                    //部分在左子区间,部分在右子区间,返回左子区间与右子区间中值大的
        return max(find1(i*2, a, mid), find1(i*2+1, mid+1, b));
}
int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        int i, num;
        for(i=1; i<=n; i++)
            scanf("%d", &s[i]);
        create(1, 1, n);
        while(m--)
        {
            char ch;
            int a, b;
            scanf("%*c%c%d%d", &ch, &a, &b);
            if(ch == 'U') change(1, a, b);
            if(ch == 'Q') printf("%d\n", find1(1, a, b));
        }
    }
    return 0;
}

 

这样就OK了   对于find1 函数给一个标记值big记录最大值耗时少了一半~ ~

void find1(int i, int a, int b)
{
    if(stu[i].left ==a && stu[i].right == b)
    {
        if(stu[i].maxx > big)
            big =  stu[i].maxx;
        return ;
    }
    int mid = (stu[i].left+stu[i].right) / 2;
    if(b<=mid)     find1(i*2, a, b);
    else if(a>mid) find1(i*2+1, a, b);
    else
    {
        find1(i*2, a, mid);
        find1(i*2+1, mid+1, b);
    }
}

 

2》 区间更新 区间求和

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4267

题目大意:      有 N 个数存储为 [1,N]。 Q次操作。    

操作类型:     

  》  C a b c 表示给区间[a, b]内每个数都加上 c     

  》  Q a b   表示求区间[a, b]内各个数的总和

思路: 线段树区间更新, 区间求和。。

代码:

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

struct node
{
    int l, r;
    __int64 sum, flag; //sum代表区间[l, r]的总和  flag代表区间[l,r]内各个数的变化值
} s[100000*4];

int x[100005];         //辅助空间存储初始的 N 个数
void create(int i, int a, int b) // 建立树
{
    s[i].l = a;
    s[i].r = b;
    s[i].flag = 0;                //各个节点无变化 初始为0
    if(a == b)
    {
        s[i].sum = x[a];
        return ;
    }
    int mid = (a+b)/2;
    create(i*2, a, mid);
    create(i*2+1, mid+1, b);
    s[i].sum = s[i*2].sum + s[i*2+1].sum; // 初始化时都为0这句可以不要~~
                                          //父节点的总和值= 左子节点总和值 + 右子节点总和值
}
void updata(int i, int a, int b, __int64 c) //给区间[a, b]每个数都加上c
{
    if(s[i].l == a && s[i].r == b)
    {
        s[i].flag += c;             //给区间[a, b]的变化值 加c
        s[i].sum += (b-a+1) * c;    //qujian[a, b]各数加c
        return ;
    }
    if(s[i].flag)                  //如果当前区间的变化值不为0, 则要对其子区间进行修改
    {
        s[i*2].flag += s[i].flag;   //修改左子区间
        s[i*2].sum += (s[i*2].r-s[i*2].l+1) * s[i].flag;
        s[i*2+1].flag += s[i].flag; //修改右子区间
        s[i*2+1].sum += (s[i*2+1].r-s[i*2+1].l+1) * s[i].flag;
        s[i].flag = 0;               //修改完成将当前区间变化值变为1
    }
    int mid = (s[i].r+s[i].l) /2;
    if(a > mid) updata(i*2+1, a, b, c);
    else if(b<=mid) updata(i*2, a, b, c);
    else
    {
        updata(i*2, a, mid, c);
        updata(i*2+1, mid+1, b, c);
    }
    s[i].sum = s[i*2].sum + s[i*2+1].sum; //当前区间总和值= 左子区间总和值+ 右子区间总和值
}

__int64 find1(int i, int a, int b)     //返回区间[a, b]总和值
{
    if(s[i].l == a && s[i].r == b) return s[i].sum;
    if(s[i].flag)                      //这个在这里不能少!!!!!
    {
        s[i*2].flag += s[i].flag;
        s[i*2].sum += (s[i*2].r-s[i*2].l+1) * s[i].flag;
        s[i*2+1].flag += s[i].flag;
        s[i*2+1].sum += (s[i*2+1].r-s[i*2+1].l+1) * s[i].flag;
        s[i].flag = 0;
    }
    if(b <= s[i*2].r)                    //区间[a,b]完全在当前区间的左子区间
        return find1(i*2, a, b);
    else if(a>=s[i*2+1].l)               //区间[a,b]完全在当前区间的右子区间
        return find1(i*2+1, a, b);
    else                                 //区间[a,b]一部分在当前区间左。。  一部分在右。。
        return find1(i*2, a, s[i*2].r) + find1(i*2+1, s[i*2+1].l, b);
}
int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        int i, a, b;
        __int64 c;
        for(i=1; i<=n; i++)
            scanf("%d", &x[i]);
        create(1, 1, n);
        while(m--)
        {
            char ch;
            scanf("%*c%c", &ch);
            if(ch == 'C')
            {
                scanf("%d%d%I64d", &a, &b, &c);
                updata(1, a, b, c);
            }
            else

            {
                scanf("%d%d", &a, &b);
                __int64 S = find1(1, a, b);
                printf("%I64d\n", S);
            }
        }
    }
    return 0;
}

 

3》 单点更新 区间求和

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166

题目大意:      给你敌人营地个数N(<=50000),和每个营地的人数(<=50)。     

有四种命令形式:    

  》 Add i j      表示第 i 营地增加 j 个人。    

  》 Sub i j      表示第  i  营地减少  j    个人。    

  》  Query i j    表示询问第 i 到第 j 个营地一共有多少人。            

  》  End            表示命令结束。

思路:线段树单点更新, 区间求和~   代码有注释

代码:

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

typedef struct node  //每个节点的结构
{
    int left, right;//每个节点所对应区间 [left, right]
    int data;        //区间内所有节点的和值           
} ss;
ss num[50005*4];

void create(int i, int a, int b)//初始化区间[a,b]
{
    num[i].data = 0;
    num[i].left = a;
    num[i].right = b;
    if(a == b) return ;
    int mid = (a+b)/2;
    create(2*i, a, mid);
    create(2*i+1, mid+1, b);
}

void change(int i, int a, int b)//对于节点a进行更新 b个数量
{
    if(num[i].left  ==num[i].right )
    {
        num[i].data += b;
        return ;
    }
    int mid = (num[i].left+num[i].right) / 2;
    if(a <= mid) change(i*2, a, b);    //节点a 在当前区间的左子区间
    else         change(i*2+1, a, b);  //节点a 在当前区间的右子区间
    num[i].data += b;                  //对于data 更新
}

int  find1(int i, int a, int b){   //返回区间[a, b]的和值
    if(num[i].left == a && num[i].right == b)    return  num[i].data;
    int mid = (num[i].left+num[i].right) / 2;
    if(mid < a)        return find1(i*2+1, a, b); //区间[a, b] 在当前区间的右子区间  
    else if(mid >= b)  return find1(i*2, a, b);   //区间[a, b] 在当前区间的左子区间 
    else          //区间[a, b] 一部分在当前区间的右子区间 一部分在左子区间  
        return  find1(i*2, a, mid)+find1(i*2+1, mid+1, b);

}
int main()
{
    int t, cas=1;
    scanf("%d", &t);
    while(t--)
    {
        printf("Case %d:\n", cas++);
        int n, i, j;
        scanf("%d", &n);
        create(1, 1, n);
        for(i=1; i<=n; i++)
        {
            scanf("%d", &j);
            change(1, i, j);
        }
        while(1)
        {
            char str[100];
            scanf("%s", str);
            if(str[0] == 'Q')
            {
                scanf("%d%d", &i, &j);
                int sum =find1(1, i, j);
                printf("%d\n", sum);
            }
            if(str[0] == 'A')
            {
                scanf("%d%d", &i, &j);
                change(1, i, j);
            }
            if(str[0] == 'S')
            {
                scanf("%d%d", &i, &j);
                change(1, i, -j);
            }
            if(str[0] == 'E') break;
        }
    }
    return 0;
}

 

》 区间最大连续值:

struct node
{
    int R, L;      
    int Rs, Ls, ms;
};
说明:
左子区间[1,100]  右子区间[101, 150]    30,75,102, 114是断点
  //父节点最大连续值 = max(左孩子的最大连续值,右孩子的最大连续值,左孩子右区间+右孩子左区间的连续值)
  //左孩子的区间没有断点,则父节点的左区间=左孩子的右区间+右孩子的左区间
  //右孩子与左孩子同理
  左子区间:R==100, L==1, Ls==30-1, Rs==101-75, ms==74-30;
  右子区间:R==150, L==101, Ls==101-75, Rs==150-114, ms==150-114;
  父区间:R==150, L==1,  Ls==30-1, Rs==150-114, ms==150-114
 
题意:
给一个连通的区间,有三种操作:
   》 D x 把x点断开
   》 Q x 问与x连通的点有多少
   》 R   修复最后一次断开的点
代码: 
#include <iostream>
#include <cstdio>
using namespace std;
#define max(a, b) (a>b?a:b)
struct node
{
    int r, l;
    int rs, ls, ms;//rs右端点的最大连续值,ls左端点的最大连续值,ms区间的最大连续值
} s[50000<<2];
int Q[50000], top; //模拟栈
void create(int i, int l, int r)
{
    s[i].l=l;
    s[i].r=r;
    s[i].ls=s[i].rs=s[i].ms=r-l+1;//开始时各个村落都是连接的
    if(l==r) return ;
    int mid=(l+r)>>1;
    create(i*2, l, mid);
    create(i*2+1, mid+1, r);
}
void updata(int i, int a, int x)
{
    if(s[i].l==s[i].r) //找到a村落
    {
        if(x) s[i].ls=s[i].rs=s[i].ms=1;   //修复
        else  s[i].ls=s[i].rs=s[i].ms=0; //破坏
        return ;
    }
    int mid=(s[i].l+s[i].r)>>1;
    if(mid < a)
        updata(i*2+1, a, x);
    else
    updata(i*2, a, x);
    s[i].ls=s[i*2].ls;       //父节点的左端点连续值 = 左孩子左端点的连续值
    s[i].rs=s[i*2+1].rs;  //父节点的右端点连续值  = 右孩子右端点的连续值
    if(s[i*2].ms==s[i*2].r-s[i*2].l+1) //左孩子的区间没有断点,则父节点的左端点=左孩子的右端点+右孩子的左端点
        s[i].ls += s[i*2+1].ls;
    if(s[i*2+1].ms==s[i*2+1].r-s[i*2+1].l+1) //右孩子同理
        s[i].rs += s[i*2].rs;
    s[i].ms=max(max(s[i*2].ms, s[i*2+1].ms), (s[i*2].rs+s[i*2+1].ls));
    
    //父节点最大连续值是左孩子的最大连续值,右孩子的最大连续值,左孩子右端点+右孩子左端点的连续值
}
int find(int i, int a)
{
    if(s[i].l==s[i].r || !s[i].ms || s[i].ms==(s[i].r-s[i].l+1)) //当前区间  最大长度空 或 无断点 或 找到目标
        return s[i].ms;
    int mid=(s[i].r+s[i].l)>>1;
    if(a<=mid)
    {
        if(a> s[2*i].r-s[2*i].rs)
        // a在左子树的右端点连续区间,则要看右子树的左端点长度+左子树右端点连续长度+a为右端点的连续长度
            return find(2*i,a)+find(2*i+1,mid+1);
        else
            return find(2*i,a); //a不在左子树的右端点连续区间,则要看a为右端点的连续长度 + a为左端点的连续长度
    }
    else
    {
        if(a < s[2*i+1].l+s[2*i+1].ls)//同理
            return find(2*i+1,a)+find(2*i,mid);
        else
            return find(2*i+1,a);
    }
}
int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m)!= EOF)
    {
        char ch[2];
        int a;
        top=0;
        create(1, 1, n);
        while(m--)
        {
            scanf("%s", ch);
            if(ch[0]=='D')
            {
                scanf("%d", &a);
                Q[top++]=a;          //将a入栈
                updata(1, a, 0);
            }
            if(ch[0]=='R' && top>0)   //栈不空
                updata(1, Q[--top], 1);
            if(ch[0]=='Q')
            {
                scanf("%d", &a);
                int sum=find(1, a);
                printf("%d\n", sum);
            }
        }
    }
    return 0;
}

 

posted @ 2015-09-21 22:04  马晨  阅读(73)  评论(0)    收藏  举报