线段树基础模板&&扫描线

线段树的单点更新+区间求和
hdu1166敌兵布阵
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
结构体实现的代码
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50005;
int num[maxn];
char str[30];
int n;
typedef struct node{
   int left,right,data;
   node* lchild;
   node* rchild;
   node(){
       left=right=data=0;
   }
}tree;
int sum;
tree* create(int a,int b){
    tree *r;
    r=(tree*)malloc(sizeof(tree));
    r->left=a;
    r->right=b;
    if(a==b){
        r->data=num[a];
        r->lchild=r->rchild=NULL;
    }
    else{
        int mid=(a+b)>>1;
        r->lchild=create(a,mid);
        r->rchild=create(mid+1,b);
        r->data=r->lchild->data+r->rchild->data;
    }
    return r;
}

void updata(tree *r,int a,int b){
    if(r->left==a&&r->right==a){
        r->data+=b;
        return;
    }
    int mid=(r->left+r->right)>>1;
    if(a<=mid)
        updata(r->lchild,a,b);
    else{
        updata(r->rchild,a,b);
    }
    r->data+=b;
}
void find(tree *r,int a,int b){
   if(r->left==a&&r->right==b){
       sum+=r->data;
       return;
   }
   int mid=(r->left+r->right)>>1;
   if(b<=mid)
    find(r->lchild,a,b);
   else if(a>mid)
    find(r->rchild,a,b);
   else{
       find(r->lchild,a,mid);
    find(r->rchild,mid+1,b);
   }
}
int main(){
  int t;
  int cas=1;
  scanf("%d",&t);
  while(t--){

      scanf("%d",&n);
      for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
      }
      tree *T;
      T=create(1,n);
      int a,b;
      printf("Case %d:
",cas++);
      while(scanf("%s",str)!=EOF){
            if(str[0]=='E')
                break;
                scanf("%d%d",&a,&b);
            if(str[0]=='A')
                updata(T,a,b);
                else if(str[0]=='S')
                updata(T,a,-b);
                else{
                        sum=0;
                    find(T,a,b);
                    printf("%d
",sum);
                    }
      }


  }
  return 0;
}

hdu1754单点更新+区间求最大值
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取'Q''U') ,和两个正整数A,B。
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
Output
对于每一次询问操作,在一行里面输出最高成绩。
Sample Input
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5 
Sample Output
5
6
5
9 
代码
数组实现
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int Maxn=200005;
struct node{
   int Max,left,right;
}tree[Maxn*3];
int val[Maxn];
int create(int root,int left,int right){
    tree[root].left=left;
    tree[root].right=right;
    if(left==right)
        return tree[root].Max=val[left];
    int a,b,mid=(left+right)>>1;
    a=create(root<<1,left,mid);
    b=create(root<<1|1,mid+1,right);
    return tree[root].Max=max(a,b);
}
int updata(int root,int pos,int val){
    if(pos<tree[root].left||tree[root].right<pos)
        return tree[root].Max;
    if(tree[root].left==pos&&tree[root].right==pos)
        return tree[root].Max=val;
    int a,b;
    a=updata(root<<1,pos,val);
    b=updata(root<<1|1,pos,val);
    return tree[root].Max=max(a,b);
}
int cal(int root,int left,int right){
    if(tree[root].left>right||tree[root].right<left)
        return 0;
    if(tree[root].left>=left&&tree[root].right<=right)
        return tree[root].Max;
    int a,b;
    a=cal(root<<1,left,right);
    b=cal(root<<1|1,left,right);
    return max(a,b);
}

int main(){
     int n,q;
     while(scanf("%d%d",&n,&q)!=EOF){
        for(int i=1;i<=n;i++)
            scanf("%d",&val[i]);
        int tmp_1=create(1,1,n);
        char c;
        getchar();
        for(int i=1;i<=q;i++){
                int a,b;
            scanf("%c %d %d",&c,&a,&b);
            getchar();
           if(c=='Q'){
              int ans=cal(1,a,b);
            printf("%d
",ans);
           }
           else{
             int tmp_2= updata(1,a,b);
           }
        }
     }
    return 0;
}

hdu1698区间更新+区间求和
在一款游戏dota中,有一个人物叫帕吉,他有一个钩子,钩子的每一节可能是不同的材质,有的是铜的,有的是银的
,有的是金的,每一节铜钩子的价值是1,银钩子的价值是2,金钩子的价值是3。帕吉现在想对钩子做一些操作,然后
求出钩子的总价值。
数据的第一行是测试数据组数。
对于每组测试数据,第一行是一个数字N,代表帕吉钩子的节数,
也就是长度;第二行是一个数字Q,代表帕吉要对钩子进行几次操作,接下来Q行,每行有三个数字X、Y和Z,代表把钩
子的第X节到第Y节修改成第Z种材质(第一种:铜;第二种:银;第三种:金)。
对于每组测试数据,输出帕吉钩子的价值。

1
10
2
1 5 2
5 9 3 
代码
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;

#define MAXSIZE 1000000

int val[MAXSIZE];
int add[100010<<2];
int sum[100010<<2];

struct node
{
    int total;
    int left;
    int right;
    int mark; //延时标记
} tree[MAXSIZE*3];
//下面两种create都可以,选择一种就可
//int create(int root,int left,int right)
//{
//    add[root]=0;
//    sum[root]=1;
//    tree[root].left=left;
//    tree[root].right=right;
//    if(left==right)
//        return tree[root].total=val[left];
//    int middle=(left+right)>>1;
//    return tree[root].total=create(root<<1,left,middle)+create(root<<1|1,middle+1,right);
//}
void create(int root,int left,int right)
{
    add[root]=0;
    sum[root]=1;//初始值为1,如果为0,有些在更新中没有更新到的节点值就为0了;
    tree[root].left=left;
    tree[root].right=right;
    if(left==right)
        return ;
    int middle=(left+right)>>1;
    create(root<<1,left,middle);
    create(root<<1|1,middle+1,right);
}
// 参数:询问区间左端点,询问区间右端点,每个位置需要增加的值,当前节点序号
void update(int L, int R, int x, int root)
{
    if (L<=tree[root].left && tree[root].right<= R)
    {
        // 当前区间被包含,处理相应附加信息
        add[root] = x;    // 更新延迟标记
        sum[root] = x * (tree[root].right-tree[root].left+1);
        return;          // 暂时不用再向下递归
    }
    int mid = (tree[root].left+tree[root].right)>>1;
    if (add[root])   // 延迟标记不为0,说明有未完成的更新,更新之
    {
        add[root<<1] = add[root];
        add[root<<1|1] = add[root];
        sum[root<<1] = add[root] * (mid-tree[root].left+1);
        sum[root<<1|1] = add[root] * (tree[root].right-mid);
        add[root] = 0; // 不要忘了去除延迟标记
    }
    if (L <= mid)   // 左子区间中包含有更新区间的部分,需要更新
        update(L, R, x, root<<1);
    if (R > mid)     // 右子区间中包含有更新区间的部分,需要更新
        update(L, R, x, root<<1|1);
    sum[root] = sum[root<<1] + sum[root<<1|1];//从叶子节点向上更新
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int i=1,n,q; i<=t; i++)
    {
        memset(sum,0,sizeof(sum));
        memset(add,0,sizeof(add));
        scanf("%d%d",&n,&q);
        for(int j=1; j<=n; j++)
            val[j]=1;
        create(1,1,n);
        for(int j=0,x,y,z; j<q; j++)
        {
            scanf("%d%d%d",&x,&y,&z);
            update(x,y,z,1);
        }
        printf("Case %d: The total value of the hook is %d.
",i,sum[1]);
    }
}

线段树的区间合并&&求区间的最长公共子序列
假设有一列数{Ai}(1≤i≤n),支持如下两种操作:


 U A B: 将第A个数变成值 B

 Q A B: 输出区间[A,B]的最长连续递增子序列的长度


区间的最大连续递增子序列(下面统称LCIS)的长度
las[i]??? i 号节点对应区间包含左端点最长的LCIS长度
ras[i]??? i 号节点对应区间包含右端点最长的LCIS长度
mov[i] 表示i节点(线段)对应的LCIS的长度
///i表示的是节点,即线段树节点
例如当前节点i 对应的区间的序列为: 

1   2   5   2   1   0   4   2   6 
 

则   las[i] = 3,  ras[i] = 2,   同时,mov[i] = 3

对于线段树中某个节点 i ,它的 mov[i] 值应该是以下几种情况的最大值:
(1)左儿子 ls 节点的 mov[ls] 值
(2) 右儿子 rs 节点的 mov[rs] 值
(3) 左右儿子区间合并之后得到的LCIS值。
从刚刚的例子中我们发现: 区间合并得到的LCIS
一定跨越左右儿子两个区间,
因此其必然经过当前节点对应区间中间的两个数!
我们可以用上面的后两个数组来表示出来,即:
    ///ras[ls]  +  las[rs]
用语言表达就是:
///从左儿子对应区间的右端点向左延伸的最大LCIS长度 加上
///从右儿子对应区间的左端点向右延伸的最大LCIS长度

首先是建树:
///建树模版
int build(int root,int l,int r)
{
    if(l==r)
    {
        scanf("%d",&a[l]);
        ras[root]=las[root]=1;
        mov[root]=1;
        return;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    pushup(l,mid,r,root);
}
void pushup(int l,int mid,int r,int root)
{
    las[root]=las[root<<1];
    ras[root]=ras[root<<1|1];
    mov[root]=0;
    if(a[mid]<a[mid+1])
    {
        mov[root]=ras[root<<1]+las[root<<1|1]
        if(las[root<<1]==mid-l+1)
            las[root]+=las[root<<1|1];
        if(ras[root<<1|1]==r-mid)
            ras[root]+=ras[root<<1];
    }
     mov[root]=max(mov[root],max(mov[root<<1],mov[root<<1|1]));
}

///查询操作
int query(int L, int R,///L,R为要查询的区间, int l, int r, int rt){
    if (L<=l && r<=R)
        return mov[rt];

    int mid=(l+r)>>1;
    int res=0;
    if (R <= mid)
        return query(L, R, l, mid, rt<<1);
    else if (L > mid)
        return query(L, R, mid+1, r, rt<<1|1);
    else {
        int t1 = 0, t2 = 0, t3 = 0;
        t1 = query(L, R, l, mid, rt<<1);
        t2 = query(L, R, mid+1, r, rt<<1|1);
        if (va[mid] < va[mid+1]) // 注意约束条件
          t3 = min(ras[rt<<1],mid-L+1) + min(las[rt<<1|1],R-mid);
        return max(t3,max(t1,t2));
    }
    return res;
}
还有一个操作:
(1)  U A B: 将第A个树变成值 B

这个是之前单点更新操作的实现类似
,唯一需要注意的地方就是维护节点信息的时候用到了和建树相同的pushup() 操作

线段树的扫描线问题
输入就是若干个矩形的坐标,求这些矩形的面积并
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
const int MAX=200+10;
int mark[MAX<<2];//记录某个区间的下底边比上底边多的个数
double sum[MAX<<2];//记录某个区间下底边比上底边多的个数总长度
double Hash[MAX];//对横坐标x离散化

struct seg //线段
{
    double l,r,h;
    int d;
    seg() {}
    seg(double x1,double x2,double H,int D):l(x1),r(x2),h(H),d(D) {}//构造函数
    bool operator<(const seg &a)const//自定义结构体排序
    {
        return h<a.h;
    }
} s[MAX];

void Upfather(int n,int left,int right){
    if(mark[n])
        sum[n]=Hash[right+1]-Hash[left];//表示该区间整个线段长度可作为底边
    else if(left == right)
        sum[n]=0;//叶子结点区间长度为0,则底边长度为0
    else 
        sum[n]=sum[n<<1]+sum[n<<1|1];
}

void Update(int L,int R,int d,int n,int left,int right){
    if(L<=left && right<=R) //该区间是当前扫描线段的一部分
    {
        mark[n]+=d;//更新上下底边之差
        Upfather(n,left,right);//更新可用底边长
        return;
    }
    int mid=left+right>>1;
    if(L<=mid)
        Update(L,R,d,n<<1,left,mid);
    if(R>mid)
        Update(L,R,d,n<<1|1,mid+1,right);
    Upfather(n,left,right);
}

int search(double key,double *x,int n){//Search函数是为了找到x坐标为key时,hash数组的下标
    int left=0,right=n-1;
    while(left<=right){
        int mid=(left+right)>>1;
        if(x[mid] == key)
            return mid;
        else if(x[mid]>key)
            right=mid-1;
        else 
            left=mid+1;
    }
    return -1;
}

int main(){
    double x1=0,y1=0,x2=0,y2=0;
    while(x1 != -2){
        int size=0;
        while(cin>>x1>>y1>>x2>>y2,x1>=0){
            if(x1>x2)swap(x1,x2);
            if(y1>y2)swap(y1,y2);
            Hash[size]=x1;
            s[size++]=seg(x1,x2,y1,1);
            Hash[size]=x2;
            s[size++]=seg(x1,x2,y2,-1);
        }
        sort(Hash,Hash+size);//把x坐标从小到大排序
        sort(s,s+size);//把线段按高度h从小到大排序
        int k=1;
        for(int i=1; i<size; ++i) //去重复端点
        {
            if(Hash[i] != Hash[i-1])
                 Hash[k++]=Hash[i];
        }
        double ans=0;
        for(int i=0; i<size; ++i){
            int L=search(s[i].l,Hash,k);
            int R=search(s[i].r,Hash,k)-1;
            Update(L,R,s[i].d,1,0,k-1);//扫描线段更新可用底边长
            ans+=sum[1]*(s[i+1].h-s[i].h);//新增加面积
        }
        cout<<ans<<endl;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/13224ACMer/p/5223537.html