線段樹基礎模板&&掃描線

線段樹的單點更新+區間求和
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:\n",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\n",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\n",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.\n",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;
}
相關文章
相關標籤/搜索