线段树练习题(不断更新中)

通过参考大神们线段树的文章,准备开始要一个一个把上面的题目做一遍了,有很多都是原来做过的,现在也再次做一遍方便以后查阅

 打过 * 的表示对别人的想法有所参考,留待以后再做一次

现在比起一开始接触线段树已经更为容易理解了,想到自己暑期集训的时候还是傻傻的背着线段树的格式在做题,不肯动脑子去思考

代码含义,觉得自己会背格式就足够了,后来也一直只会套模版题稍微一变就傻了,现在想想当时懒得动脑筋确实是一个笑话

现在想想最简单来看就是把线段树看成一个能够维护一些自己所需数据的二叉树,通过所维护的数据来判断自己是否需要向下维护左子树

和右子树记得回溯即可

 

对于线段树的题目来说,有一类题目总是有两种关系组成,我们总是利用排序避免其中一种关系,利用另外一种关系在线段树中进行操作

 

(1)HDU 1166 敌兵布阵

单点增减,区间和查询

线段树:

#include <cstdio>

#include <cstring>



using namespace std; const int N = 50005; #define ls o<<1 #define rs o<<1|1 int a[N]; char s[10]; struct Tree{ int l , r , sum; }tree[N<<2]; void build(int o , int l , int r) { int m = (l + r) >> 1; tree[o].l = l , tree[o].r = r; if(l == r){ tree[o].sum = a[l]; return ; } build(ls , l , m); build(rs , m+1 , r); tree[o].sum = tree[ls].sum + tree[rs].sum; } void update(int o , int i , int j , int op) { if(tree[o].l == tree[o].r && tree[o].r == i){ if(op == 1) tree[o].sum += j; if(op == 2) tree[o].sum -= j; return ; } int m = (tree[o].l + tree[o].r) >> 1; if(m >= i) update(ls , i , j , op); if(m+1 <= i) update(rs , i , j , op); tree[o].sum = tree[ls].sum + tree[rs].sum; } int query(int o , int s , int t) { if(tree[o].l >= s && tree[o].r <= t) return tree[o].sum; int m = (tree[o].l + tree[o].r) >> 1; int ans = 0; if(m >= s) ans += query(ls , s , t); if(m+1 <= t) ans += query(rs , s , t); return ans; } int main() { // freopen("a.in" , "r" , stdin); int T , cas = 0; scanf("%d" , &T); while(T--){ int n , aa , bb; scanf("%d" , &n); for(int i = 1 ; i<=n ; i++) scanf("%d" , &a[i]); build(1 , 1 , n); printf("Case %d:\n" , ++cas); while(scanf("%s" , s)) { if(s[0] == 'E') break; scanf("%d%d" , &aa , &bb); if(s[0] == 'A') update(1 , aa , bb , 1); else if(s[0] == 'S') update(1 , aa , bb , 2); else printf("%d\n" , query(1 , aa , bb)); } } return 0; }
View Code

堆序列:

#include <cstdio>

#include <cstring>



using namespace std; const int N = 50005; int a[N] , sum[N<<2] , k; char s[10]; void build(int n) { memset(sum , 0 , sizeof(sum)); k = 1; while(k < n+2) k<<=1; for(int i = 1 ; i<=n ; i++) sum[k+i] = a[i]; for(int i = k-1 ; i>=1 ; i--) sum[i] = sum[i<<1] + sum[i<<1|1]; } void update(int o) { for(int i = o ; i^1 ; i>>=1) sum[i>>1] = sum[i] + sum[i^1]; } int query(int s , int t) { int i = k+s-1 , j = k+t+1; int ans = 0; for(; i^j^1 ; i>>=1 , j>>=1){ if(~i & 1) ans += sum[i^1]; if(j & 1) ans += sum[j^1]; } return ans; } int main() { // freopen("a.in" , "r" , stdin); int T , cas = 0; scanf("%d" , &T); while(T--){ int n , aa , bb; scanf("%d" , &n); for(int i = 1 ; i<=n ; i++) scanf("%d" , &a[i]); build(n); printf("Case %d:\n" , ++cas); while(scanf("%s" , s)) { if(s[0] == 'E') break; scanf("%d%d" , &aa , &bb); if(s[0] == 'A'){ sum[aa+k] += bb; update(aa+k); } else if(s[0] == 'S'){ sum[aa+k] -= bb; update(aa+k); } else printf("%d\n" , query(aa , bb)); } } return 0; }
View Code

(2)HDU 1754

老师对学生成绩的更新,找某一区间内学生成绩的最大值

线段树:

#include <cstdio>

#include <cstring>



using namespace std; #define ls o<<1 #define rs o<<1|1 const int N = 200005; int score[N] , maxn[N<<2] , ans; char s[5]; void build(int o , int l , int r) { if(l == r){ maxn[o] = score[l]; return ; } int m = (l+r)>>1; build(ls , l , m); build(rs , m+1 , r); maxn[o] = maxn[ls] > maxn[rs] ? maxn[ls] : maxn[rs]; } void update(int o , int l , int r , int i , int v) { if(l == r && r == i){ maxn[o] = v; return ; } int m = (l+r) >> 1; if(m >= i) update(ls , l , m , i , v); if(m < i) update(rs , m+1 , r , i , v); maxn[o] = maxn[ls] > maxn[rs] ? maxn[ls] : maxn[rs]; } void query(int o , int l , int r , int s , int t) { if(l >= s && r<=t){ ans = ans > maxn[o] ? ans : maxn[o]; return; } int m = (l+r) >> 1; if(m >= s) query(ls , l , m , s , t); if(m < t) query(rs , m+1 , r , s , t); } int main() { // freopen("a.in" , "r" , stdin); int n , m , a , b; while(~scanf("%d%d" , &n , &m)) { for(int i = 1; i<= n ;i++) scanf("%d" , score+i); build(1 , 1 , n); for(int i = 0 ; i<m ; i++) { scanf("%s%d%d" , s , &a , &b); if(s[0] == 'U') update(1 , 1 , n , a , b); else{ ans = 0; query(1 , 1 , n , a , b); printf("%d\n" , ans); } } } return 0; }
View Code

堆序列:

#include <cstdio>

#include <cstring>



using namespace std; const int N = 200005; int maxn[N<<2] , D , score[N]; char s[5]; void build(int n) { D = 1; memset(maxn , 0 , sizeof(maxn)); while(D < n+2) D <<= 1; for(int i = 1 ; i<=n ; i++) maxn[D+i] = score[i]; for(int i = D-1 ; i>=1 ; i--) maxn[i] = maxn[i<<1] > maxn[i<<1|1] ? maxn[i<<1] : maxn[i<<1|1]; } void update(int o) { for(int i = D + o ; i^1 ; i>>=1) maxn[i>>1] = maxn[i] > maxn[i^1] ? maxn[i] : maxn[i^1]; } int query(int a , int b) { int i = D + a - 1 , j = D + b +1; int ans = 0; for(; i^j^1 ; i>>=1 , j>>=1) { if(~i & 1) ans = ans > maxn[i^1] ? ans : maxn[i^1]; if(j & 1) ans = ans > maxn[j^1] ? ans : maxn[j^1]; } return ans; } int main() { // freopen("a.in" , "r" , stdin); int n , m , a , b; while(~scanf("%d%d" , & n , &m)) { for(int i = 1 ; i<=n ; i++) scanf("%d" , score+i); build(n); for(int i =0 ; i<m ; i++){ scanf("%s%d%d" , s , &a , &b); if(s[0] == 'U') { maxn[D+a] = b; update(a); } else printf("%d\n" , query(a , b)); } } return 0; }
View Code

(3)HDU 2795 BillBoard

将海报一张张贴在墙上,总希望贴在最上方,然后往最左侧贴

每次输出海报贴在墙上的位置,如果贴不上去输出-1

这里就是关于建立线段树的小问题

实际上h可以取到10^9是没有必要的,h最大也就是n的最大值200000

具体看代码最上方

/* 因为每张海报最多只能占据一行,否则贴不上去 所以高度h最大取到200000即可,10^9就是吓唬人的 所以维护200000行的剩余宽度,每次取最大值 */ #include <cstdio> #include <cstring> #include <iostream> using namespace std; const int N = 200005; #define ls o<<1 #define rs o<<1|1 int maxn[N<<2] , left[N] , w , h; void build(int o , int l , int r , int w) { maxn[o] = w; if(l == r) return; int m = (l+r) >> 1; build(ls , l , m , w); build(rs , m+1 , r , w); } void update(int o , int l , int r , int i , int v) { int m = (l+r) >> 1 ; if(l == r && r == i){ maxn[o] -= v; return; } if(m >= i) update(ls , l , m , i , v); if(m < i) update(rs , m+1 , r , i ,v); maxn[o] = max(maxn[ls] , maxn[rs]); } int query(int o , int l , int r , int v) { if(maxn[o] < v) return -1; if(l == r) return l; int m = (l+r) >> 1; int ans; if(maxn[ls] >= v) ans = query(ls , l , m , v); else ans = query(rs , m+1 , r , v); return ans; } int main() { // freopen("a.in" , "r" , stdin); int h , w , n; while(~scanf("%d%d%d" , &h , &w , &n)) { int t = min(n , h); build(1 , 1 , t , w); for(int i = 0 ; i<n ; i++) { int a; scanf("%d" , &a); int ans = query(1 , 1 , t , a); printf("%d\n" , ans); if(ans > 0) update(1 , 1 , t , ans , a); } } return 0; }
View Code

 

(4)POJ 2828 插队问题

一个数插到某个位置上,其后面的数字都要往后推移,最后输出整个序列

用线段树维护一个总空位的值

大概思路看代码上方

/* 这里的数理解为自己前方有多少个 数在自己前面 在线段树上记录对应节点的位置总 共有多少个空位 因为前面的数会受后面的数的影响 但是后面的数肯定不收前面的影响 所以可以先插入后面的数到固定位置 往前不断插入数字,碰到左子树有足够 的空位就往左移,如果不够,右移 所需位置要减去左侧位置 */ #include <cstdio> #include <cstring> #include <iostream> using namespace std; const int N = 200005; #define ls o<<1 #define rs o<<1|1 int sum[N<<2] , left[N] , id[N] , rec[N] , val[N]; //sum记录这个节点对应的区间还有多少位置 void push_up(int o) { sum[o] = sum[ls] + sum[rs]; } void build(int o , int l , int r) { if(l == r){ sum[o] = 1; return; } int m = (l+r) >> 1; build(ls , l , m); build(rs , m+1 , r); push_up(o); } void update(int o , int l , int r , int i) { int m = (l+r) >> 1 ; if(l == r && r == i){ sum[o] = 0; return; } if(m >= i) update(ls , l , m , i); if(m < i) update(rs , m+1 , r , i); push_up(o); } int query(int o , int l , int r , int v) { if(l == r) return l; int m = (l+r) >> 1; int ans; if(sum[ls] >= v) ans = query(ls , l , m , v); else ans = query(rs , m+1 , r , v - sum[ls]); return ans; } int main() { // freopen("a.in" , "r" , stdin); int n; while(~scanf("%d" , &n)) { build(1 , 1 , n); for(int i = 1 ; i<= n ; i++){ scanf("%d%d" , &id[i] , &val[i]); } for(int i = n ; i>=1 ; i--){ int tmp = query(1 , 1 , n , id[i]+1); rec[tmp] = val[i]; update(1 , 1 , n , tmp); } for(int i = 1 ; i<=n ; i++) printf("%d " , rec[i]); puts(""); } return 0; }
View Code

 

(5)*POJ 1394

给定一堆序列,来判断所有i>j , a[i] > a[j]的组数 , 另外可以不断把收个元素放在后面 , 这个序列的元素是由0~n-1组成

 

往往碰到添加 n 个连续序号的题目都可以通过不断将元素添入线段树对应的 i 位置逐个维护

因为是0~n-1所以可以通过一个个添加节点实现,我这里用的是逆向添加节点,每次判断添加元素的前方已经有几个位置放了元素,那么

这么多个位置就可以跟当前添加元素形成的对数

另外每次将首个数放到最后方

减少的是 a[i] - 1 (a[i]从1开始) , 我自己题目为了自己好理解把所有字母 加 了1 , 本来是从0开始 的

增加的是 n - a[i] ,然后不断更新最小值即可

自己一开始对最后的更新n一直找不到线性的方法还是看了别人的思路得到的,自己还要不断进步啊

 

#include <cstdio>

#include <cstring>

#include <iostream>

using namespace std;



#define ls o<<1

#define rs o<<1|1

const int N = 50010;



int sum[N<<2] , num[N]; //维护线段树上空余节点的总个数



void build(int o , int l , int r)

{

    int m = (l+r) >> 1;

    sum[o] = 0;

    if(l == r) return;

    build(ls , l , m);

    build(rs , m+1 , r);

}



void update(int o , int l , int r , int i)

{

    int m = (l + r) >> 1;

    if(l == r && r == i){

        sum[o] = 1;

        return;

    }

    if(m >= i) update(ls , l , m , i);

    else update(rs , m+1 , r , i);

    sum[o] = sum[o<<1] + sum[o<<1|1];

}



int query(int  o , int l , int r , int i)

{

    int m = (l+r) >> 1;

    if(r < i) return sum[o];

    if(l >= i) return 0;

    int ans = 0;

    if(m >= i) ans += query(ls , l , m , i);

    else ans += query(rs , m+1 , r , i) + sum[ls];

    return ans;

}



int main()

{

  //  freopen("a.in" , "r" , stdin);

    int n;

    while(~scanf("%d" , &n))

    {

        int ans = (1<<30) , tmp = 0;

        build(1 , 1 , n);

        for(int i = 0 ; i<n ; i++)

        {

            scanf("%d" , num+i);

            num[i]++;

        }

        for(int i = n-1 ; i>=0 ; i--){

            tmp += query(1 , 1 , n , num[i]);

            update(1 , 1 , n , num[i]);

        }

        ans = min(ans , tmp);

        for(int i = 0 ; i<n ; i++){

            tmp -= num[i]-1;

           // cout<<"tmp1: "<<tmp<<endl;

            tmp += n-num[i];

           // cout<<"tmp2: "<<tmp<<endl;

            ans = min(ans , tmp);

        }

        printf("%d\n" , ans);

    }

    return 0;

}
View Code

 

(6)HDU 3397

3种更新操作 ,区间置0 , 区间置1 , 区间内所有数完成 0->1 , 1->0的转化

2种询问操作, 区间求和 , 求区间内连续 1 最多的个数。

维护求和的值, 还有 0 , 1的最长连续的值

题目不难,但代码很长容易出错。。。

/*

区间转化分别维护一个 1 和一个 0 的连续最大,及左右区间

代码量很长很烦。。。

每次to标志的更新都要记得将rev标志更新为0

而rev标志无法影响to标志

*/



#include <cstdio>

#include <cstring>

#include <iostream>

using namespace std;

const int N = 100005;

#define ls o<<1

#define rs o<<1|1

int ml[N<<2][2] , mr[N<<2][2] , ma[N<<2][2] , sum[N<<2] , num[N];



struct Tree{

    int l , r , len , to , rev;

}tree[N<<2];



void push_up(int o)

{

    //更新和

    sum[o] = sum[ls] + sum[rs];

    //更新区间

    for(int i = 0 ; i<2 ; i++)

    {

        ma[o][i] = ml[rs][i] + mr[ls][i];

        ma[o][i] = max(ma[rs][i] , ma[ls][i]);

        ma[o][i] = max(ma[o][i] , ml[rs][i] + mr[ls][i]);

        ml[o][i] = ml[ls][i];

        mr[o][i] = mr[rs][i];

        if(ml[ls][i] == tree[ls].len)

            ml[o][i] = ml[ls][i] + ml[rs][i];

        if(mr[rs][i] == tree[rs].len)

            mr[o][i] = mr[rs][i] + mr[ls][i];

    }

}



void _swap(int &a , int &b)

{

    int tmp = a;

    a = b , b = tmp;

}



void To(int o , int l , int r  , int v)

{

    sum[o] = v*tree[o].len;



    ma[o][v] = ml[o][v] = mr[o][v] = tree[o].len;

    ma[o][v^1] = ml[o][v^1] = mr[o][v^1] = 0;

}



void Rev(int o , int l , int r)

{

    //更新和

    sum[o] = tree[o].len - sum[o];



    //更新区间

    _swap(ma[o][0] , ma[o][1]);

    _swap(ml[o][0] , ml[o][1]);

    _swap(mr[o][0] , mr[o][1]);

}



void push_down(int o)

{

    int m = (tree[o].l+tree[o].r) >> 1;

    if(tree[o].to >= 0){

        tree[ls].to = tree[rs].to = tree[o].to;

        //往下传递时,to标志会将rev标志覆盖掉

        tree[ls].rev = tree[rs].rev = 0;



        To(ls , tree[o].l , m , tree[o].to);

        To(rs , m+1 , tree[o].r , tree[o].to);



        tree[o].to = -1;

    }

    if(tree[o].rev){

        tree[ls].rev ^= 1 , tree[rs].rev ^= 1;



        Rev(ls , tree[o].l , m);

        Rev(rs , m+1 , tree[o].r);



        tree[o].rev = 0;

    }

}



void build(int o , int l , int r)

{

    int m = (l+r)>>1;

    tree[o].l = l , tree[o].r = r , tree[o].len = r - l + 1;

    tree[o].to = -1;

    tree[o].rev = 0;

    if(l == r){

        if(num[l] == 0){

            ma[o][1] = ml[o][1] = mr[o][1] = sum[o] = 0;

            ma[o][0] = ml[o][0] = mr[o][0] = 1;

        }else{

            ma[o][1] = ml[o][1] = mr[o][1] = sum[o] = 1;

            ma[o][0] = ml[o][0] = mr[o][0] = 0;

        }

        return ;

    }

    build(ls , l , m);

    build(rs , m+1 , r);

    push_up(o);

}



void update(int o , int l , int r , int s , int t , int op)

{

    if(l >= s && r <= t){

        if(op == 0){

            tree[o].to = 0 , tree[o].rev = 0;

            To(o , l , r , 0);

        }else if(op == 1){

            tree[o].to = 1 , tree[o].rev = 0;

            To(o , l , r , 1);

        }else if(op == 2){

            tree[o].rev ^= 1;

            Rev(o , l ,r);

        }

        return;

    }

    push_down(o);

    int m = (l+r) >> 1;

    if(m >= s) update(ls , l , m , s , t , op);

    if(m < t) update(rs , m+1 , r , s , t , op);

    push_up(o);

}



int query1(int o , int l , int r , int s , int t)

{

    if(l >= s && r<=t) return sum[o];

    push_down(o);

    int ans = 0 , m = (l+r) >> 1;

    if(m >= s) ans += query1(ls , l , m , s , t);

    if(m < t) ans += query1(rs , m+1 , r , s , t);

    return ans;

}



int query2(int o , int l , int r , int s , int t)

{

    if(l >= s && r<=t) return ma[o][1];

    push_down(o);

    int ans = 0 , m = (l+r) >> 1;

    if(m >= s) ans = max(query2(ls , l , m , s , t) , ans);

    if(m < t) ans = max(query2(rs , m+1 , r , s , t) , ans);

    ans = max(ans , min(mr[ls][1] , m-s+1) + min(ml[rs][1] , t-m));

    return ans;

}



int main()

{

  //  freopen("a.in" , "r" , stdin);

    int T;

    scanf("%d" , &T);

    while(T--){

        int n , m , op , s , t;

        scanf("%d%d" , &n , &m);

        for(int i = 1 ; i<= n ; i++)

            scanf("%d" , num+i);

        build(1 , 1 , n);

        for(int i = 0 ; i<m ; i++)

        {

            scanf("%d%d%d" , &op , &s , &t);

            s++ , t++;

            if(op < 3) update(1 , 1 , n , s , t , op);

            else if(op == 3) printf("%d\n" , query1(1 , 1 , n , s , t));

            else printf("%d\n" , query2(1 , 1 , n , s , t));

        }

    }

    return 0;

}
View Code

 

(7)POJ 3468 A Simple Problem with Integers

只有将区间内所有数加上一个数

询问是区间求和

 1 #include <cstdio>

 2 #include <cstring>

 3 

 4 using namespace std;

 5 const int N  = 100005;

 6 #define ls o<<1

 7 #define rs o<<1|1

 8 #define ll long long

 9 

10 int num[N] , add[N<<2];

11 ll sum[N<<2];

12 

13 void push_up(int o)

14 {

15     sum[o] = sum[ls] + sum[rs];

16 }

17 

18 void push_down(int o , int l , int r)

19 {

20     int m = (l+r) >> 1;

21     if(add[o]){

22         sum[ls] += (ll)add[o] * (m - l + 1);

23         sum[rs] += (ll)add[o] * (r - m);

24         add[ls] += add[o];

25         add[rs] += add[o];

26         add[o] = 0;

27     }

28 }

29 

30 void build(int o , int l , int r)

31 {

32     int m = (l+r) >> 1;

33     add[o] = 0;

34     if(l == r){

35         sum[o] = num[l];

36         return ;

37     }

38     build(ls , l , m);

39     build(rs , m+1 , r);

40     push_up(o);

41 }

42 

43 void update(int o , int l , int r , int s , int t , int v)

44 {

45     int m = (l+r) >> 1;

46     if(l >= s && r <= t){

47         add[o] += v;

48         sum[o] += (ll)v * (r - l + 1);

49         return ;

50     }

51     push_down(o , l , r);

52     if(m >= s) update(ls , l , m , s , t , v);

53     if(m < t) update(rs , m+1 , r , s , t , v);

54     push_up(o);

55 }

56 

57 void query(int o , int l , int r , int s , int t , ll &ans)

58 {

59     int m = (l+r) >> 1;

60     if(l >= s && r <= t)

61     {

62         ans += sum[o];

63         return ;

64     }

65     push_down(o , l , r);

66     if(m >= s) query(ls , l , m , s , t , ans);

67     if(m < t) query(rs , m+1 , r , s , t , ans);

68 }

69 

70 int main()

71 {

72    // freopen("a.in" ,"r" , stdin);

73     int n , m;

74     char str[5];

75     int s , t , v;

76     while(scanf("%d%d" , &n , &m) == 2)

77     {

78         for(int i = 1 ;  i<=n ; i++)

79             scanf("%d" , num+i);

80         build(1 , 1 , n);

81         for(int i = 0 ; i<m ; i++){

82             scanf("%s" , str);

83             if(str[0] == 'C'){

84                 scanf("%d%d%d" , &s , &t , &v) ;

85                 update(1 , 1, n , s , t , v);

86             }else{

87                 scanf("%d%d" , &s , &t);

88                 ll ans = 0;

89                 query(1, 1, n , s , t , ans);

90                 printf("%I64d\n" , ans);

91             }

92         }

93     }

94     return 0;

95 }
View Code

 

(8)HDU 1542 Atlantics 线段树+离散化扫描

求矩形的并面积,我也是看了很多大神的博客才终于看懂,实际上自己也无法通过画图讲清楚。。。

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <iostream>

using namespace std;

const int N = 205;

#define ls o<<1

#define rs o<<1|1

#define eps 1e-9



double sum[N<<2]  , y[N<<1];

int flag[N<<2];



struct Seg{

    double y1 , y2 , x ;

    int d;

    Seg(){}

    Seg(double y1 , double y2 , double x , int d):y1(y1),y2(y2),x(x),d(d){}

    bool operator<(const Seg &m)const {

        return x < m.x;

    }

}s[N<<1];



double myabs(double x)

{

    return x>0?x:-x;

}



void push_up(int o , int l , int r)

{

    if(flag[o]) sum[o] = y[r] - y[l-1];

    else if(l == r) sum[o] = 0;

    else sum[o] = sum[ls] + sum[rs];

}



void update(int o , int l , int r , int s , int t , int d)

{

    if(l >= s && r <= t){

        flag[o] += d;

        push_up(o , l , r);

        return;

    }

    int m = (l+r) >> 1;

    if(m >= s) update(ls , l , m , s , t , d);

    if(m < t) update(rs , m+1 , r , s , t , d);

    push_up(o , l , r);

}



int bin_search(double key , int l , int r)

{

    while(l <= r)

    {

        int m = (l + r) >> 1;

        if(myabs(y[m] - key) < eps) return m;

        else if(y[m] < key) l = m+1;

        else r = m-1;

    }

}



int main()

{

  //  freopen("a.in" , "r" , stdin);

    int n , cas = 0;

    double x1 , y1 , x2 , y2;

    while(scanf("%d" , &n) , n)

    {

        int k = 0;

        for(int i = 0 ; i<n ; i++)

        {

            scanf("%lf%lf%lf%lf" , &x1 , &y1 , &x2 , &y2);

            y[k] = y1;

            s[k++] = Seg(y1 , y2 , x1 , 1);

            y[k] = y2;

            s[k++] = Seg(y1 , y2 , x2 , -1);

        }

        sort(y , y+k);

        sort(s , s+k);



        int t = unique(y , y+k) - y;



        double ans = 0;

        

        memset(sum , 0 , sizeof(sum));

        memset(flag , 0 , sizeof(flag));

        /*因为添加最后一条边是无意义的,但是添加最后一条边

        会起到清空数据的作用,如果下方i < k,那么上方的memset是

        不必要的,我在这里错了好多回以示警戒*/

        for(int i = 0 ; i<k-1 ; i++){

            int pos1 = bin_search(s[i].y1 , 0 , t-1)+1;

            int pos2 = bin_search(s[i].y2 , 0 , t-1);

         //   cout<<"pos: "<<pos1<<" "<<pos2<<endl;

            update(1 , 1 , t-1 , pos1 , pos2 , s[i].d);

            ans += sum[1] * (s[i+1].x - s[i].x);

        }

        printf("Test case #%d\nTotal explored area: %.2lf\n\n" , ++cas , ans);

    }

    return 0;

}
View Code


除了网上大多数采用的二分搜离散化的编号外,也写了另一种较易理解的线段树

/*

较为普通的做法

直接添加边长度

直接进行更新

没有二分搜索离散后的编号来的好

*/

#include <cstring>

#include <cstdio>

#include <algorithm>

#include <iostream>



using namespace std;

const int N = 5005;

double sum[N<<2] , y[N<<1];

int flag[N<<2];



struct Seg{

    double y1 , y2 , x ;

    int d;

    Seg(){}

    Seg(double y1 , double y2 , double x , int d):y1(y1),y2(y2),x(x),d(d){}

    bool operator<(const Seg &m)const {

        return x < m.x;

    }

}s[N<<1];



void build(int o , int l , int r)

{

    int m = (l + r) >> 1;

    sum[o] = 0;

    flag[o] = 0;

    if(l == r-1) return;

    build(o<<1 , l , m);

    build(o<<1|1 , m , r);

}



void push_up(int o , int  l , int r)

{

    if(flag[o]) sum[o] = y[r] - y[l];

    else if(l == r-1) sum[o] = 0;

    else sum[o] = sum[o<<1] + sum[o<<1|1];

}



void update(int o , int l , int r , double y1 , double y2 , int d)

{

    if(y[l] >= y1 && y[r] <= y2)

    {

        flag[o] += d;

        push_up(o , l , r);

        return;

    }

    if(l == r-1) return;

    int m = (l+r) >> 1;

    if(y[m] >= y1) update(o<<1 , l , m , y1 , y2 , d);

    if(y[m] < y2) update(o<<1|1 , m , r , y1 , y2 , d);

    push_up(o , l , r);

}



int main()

{

   // freopen("a.in" , "r" , stdin);

    int n , cas = 0;

    double x1 , y1 , x2 , y2;

    while(scanf("%d" , &n) , n)

    {

        int k = 0;

        for(int i = 0 ; i<n ; i++)

        {

            scanf("%lf%lf%lf%lf" , &x1 , &y1 , &x2 , &y2);

            y[k] = y1;

            s[k++] = Seg(y1 , y2 , x1 , 1);

            y[k] = y2;

            s[k++] = Seg(y1 , y2 , x2 , -1);

        }

        sort(y , y+k);

        sort(s , s+k);



        int t = unique(y , y+k) - y;

        build(1 , 0 , t-1);



        double ans = 0;

        for(int i = 0 ; i<k ; i++){

            update(1 , 0 , t-1 , s[i].y1 , s[i].y2 , s[i].d);

            ans += sum[1] * (s[i+1].x - s[i].x);

        }

        printf("Test case #%d\nTotal explored area: %.2lf\n\n" , ++cas , ans);

    }

    return 0;

}
View Code

 

(9)POJ 1177 矩形的周长并

也是最近才开始看离散化加扫描线的线段树,将第一个Atlantics的这样的题我花了好长时间,而Atlantics也确实这一类题目中需要考虑因素最少的了

这道题目在看完别人的思路后自己一遍敲过了,100多行的线段树难得一次过也确实有点激动的

对于离散+扫描线段树的题目,我们确实要先弄清楚需要我们在线段树上维护的是哪一些值,逐一添加线段进入线段树,清楚有效线段在题目中所起的重要作用

这里的有效线段和求矩形面积有点小不同,因为被覆盖过的地方是不用再添加线段的,所以我们总是用la的值记录上一次的有效线段,每次和当前的有效线段

作差值得到竖直方向上的增加长度,而水平长度增加则总是伴随着一个连续区间对应两条线延伸出去

  1 /*

  2 求矩形合并后的周长

  3 将y离散化

  4 沿着x轴方向扫描线移动

  5 每次计算有效边的总长度

  6 减去上一次的有效边长度即为当前竖直方向增加的长度

  7 有效边分割成了多少区间(n个),那么就有2n条水平线往后延伸

  8 这就是 2*n*(s[i+1].x-s[i].x)为水平方向增加的长度

  9 所以我们这里主要维护sum值

 10 和区间段数的值

 11 但是区间段数的维护比较复杂,我们需要利用一些参数的帮助

 12 具体可看Tree结构中维护的参数

 13 */

 14 #include <cstdio>

 15 #include <cstring>

 16 #include <algorithm>

 17 #include <iostream>

 18 #define ls o<<1

 19 #define rs o<<1|1

 20 using namespace std;

 21 const int N = 5005;

 22 

 23 int y[N<<1];

 24 

 25 struct Seg{

 26     int y1 , y2 , x , d;

 27     Seg(){}

 28     Seg(int y1 , int y2 , int x , int d):y1(y1),y2(y2),x(x),d(d){}

 29     bool operator<(const Seg &m) const{

 30         return x < m.x;

 31     }

 32 }s[N<<1];

 33 

 34 struct Tree{

 35     int l , r;

 36     int cnt; //记录覆盖次数,才能了解是否真的有效

 37     int segment , sum; //连续区间段数和总和,主要维护参数

 38     bool lPoint , rPoint; //最左端和最右端是否为有效边,方便连续区间段数的统计

 39 }tree[N<<3];

 40 

 41 void build(int o , int l , int r)

 42 {

 43     int m = (l+r) >> 1;

 44     tree[o].l = l , tree[o].r = r;

 45     tree[o].segment = tree[o].sum = tree[o].cnt = 0;

 46     tree[o].lPoint = tree[o].rPoint = false;

 47     if(l == r-1)

 48         return;

 49     build(ls , l , m);

 50     /*因为这里l , r表示的是一个点的位置,

 51     所以不能用m+1 , r,会空出(m,m+1)这条线段没被维护*/

 52     build(rs , m , r);

 53 }

 54 

 55 int bin_search(int key , int n)

 56 {

 57     int l = 0 , r = n-1;

 58     while(l <= r)

 59     {

 60         int m = (l + r) >> 1;

 61         if(y[m] == key) return m;

 62         else if(y[m] > key) r = m-1;

 63         else l = m+1;

 64     }

 65 }

 66 

 67 void push_up(int o)

 68 {

 69     if(tree[o].cnt){

 70         tree[o].lPoint = tree[o].rPoint = true;

 71         tree[o].segment = 1;

 72         tree[o].sum = y[tree[o].r] - y[tree[o].l];

 73     }

 74     else if(tree[o].r == tree[o].l + 1){

 75         tree[o].lPoint = tree[o].rPoint = false;

 76         tree[o].segment = 0;

 77         tree[o].sum = 0;

 78     }

 79     else{

 80         tree[o].lPoint = tree[ls].lPoint;

 81         tree[o].rPoint = tree[rs].rPoint;

 82         tree[o].segment = tree[ls].segment + tree[rs].segment;

 83          //说明左右可形成一块新的连续区域,所以要把重复的减去

 84         if(tree[ls].rPoint && tree[rs].lPoint)

 85             tree[o].segment --;

 86         tree[o].sum = tree[ls].sum + tree[rs].sum;

 87     }

 88 }

 89 

 90 void update(int o , int l , int r , int s , int t , int d)

 91 {

 92     int m = (l+r) >> 1;

 93     if(l >= s && r <= t){

 94         tree[o].cnt += d;

 95         push_up(o);

 96         return;

 97     }

 98     if(l == r-1) return;

 99     if(m >= s) update(ls , l , m , s , t , d);

100     if(m < t) update(rs , m , r , s , t , d);

101     push_up(o);

102 }

103 

104 int main()

105 {

106   //  freopen("a.in" , "r" , stdin);

107     int n , x1 , x2 , y1 , y2;

108     while(scanf("%d" , &n) == 1)

109     {

110         int k = 0;

111         for(int i = 0 ; i<n ; i++){

112             scanf("%d%d%d%d" , &x1 , &y1 , &x2 , &y2);

113             y[k] = y1;

114             s[k++] = Seg(y1 , y2 , x1 , 1);

115             y[k] = y2;

116             s[k++] = Seg(y1 , y2 , x2 , -1);

117         }

118         sort(y , y+k);

119         sort(s , s+k);

120         //去重

121         int t = unique(y , y+k) - y;

122         //构建线段树

123         build(1 , 0 , t-1);

124 

125         int la = 0 , ans = 0; //la记录上一次的有效边的长度,用于和下一次有效边作差值计算

126         //逐个添加边进入线段树

127         for(int i = 0 ; i<k ; i++)

128         {

129             int pos1 = bin_search(s[i].y1 , t);

130             int pos2 = bin_search(s[i].y2 , t);

131           //  cout<<"i: "<<i<<" "<<pos1 << " "<<pos2<<endl;

132             update(1 , 0 , t-1 , pos1 , pos2 , s[i].d);

133             ans += abs(la - tree[1].sum);

134             ans += tree[1].segment * 2 * (s[i+1].x - s[i].x);

135             la = tree[1].sum; //不断更新当前有效边

136           //  cout<<"ans: "<<ans<<endl;

137         }

138         

139         printf("%d\n" , ans);

140     }

141     return 0;

142 }
View Code

 

(10)HDU 1225 矩阵交的面积

个人觉得前面两道题做完之后这道题已经不是问题了,但对于何时结束递归还是需要多多考虑的

 当然自己这个方法有点渣,时间上了近2000ms,又去看了下别人的思路发现不用更新到树底部的方法确实快很多

自己的更新到树底的代码:

/*

矩形的合并面积

当覆盖次数大于1时才将面积添入

*/

#include <cstdio>

#include <cstring>

#include <iostream>

#include <algorithm>

using namespace std;

const int N = 2010;

#define ls o<<1

#define rs o<<1|1



struct Seg{

    double y1 , y2 , x ;

    int d;

    Seg(){}

    Seg(double y1 , double y2 , double x , int d):y1(y1),y2(y2),x(x),d(d){}

    bool operator<(const Seg &m)const{

        return x < m.x;

    }

}s[N];



int flag[N<<2];

double y[N] , sum[N<<2];



void push_up(int o , int l , int r)

{

    if(flag[o] > 1) sum[o] = y[r+1] - y[l];

    else if(l == r) sum[o] = 0;

    else sum[o] = sum[ls]+sum[rs];

}



void update(int o , int l , int r , int s , int t , int d)

{

    int m = (l+r) >> 1;

    if(l >= s && r <= t){

        flag[o] += d;

        push_up(o , l , r);

        /*这题目跟矩形合并面积有所不同

        ,这里必须不断更新flag到最底层

        因为flag要求>1才行,二矩阵并只要flag>0

        即可那么提前结束递归只是取消当前对应边,

        所以不影响结果,而这里当前边的删除会影响

        后面的边*/

    }

    if(l == r) return;

   // push_down(o , l , r);

    if(m >= s) update(ls , l , m , s , t , d);

    if(m < t) update(rs , m+1 , r , s , t , d);

    push_up(o , l , r);

}



int bin_search(double key , int n)

{

    int l = 0 , r = n - 1;

    while(l <= r){

        int m = (l+r) >> 1;

        if(y[m] == key) return m;

        else if(y[m] < key) l = m+1;

        else r = m-1;

    }

}



int main()

{

  //  freopen("a.in" , "r" , stdin);

    int T;

    double x1 , y1 , x2 , y2;

    scanf("%d" , &T);

    while(T--){

        int n , k=0;

        scanf("%d" , &n);

        for(int i = 0 ; i < n ; i++)

        {

            scanf("%lf%lf%lf%lf" , &x1 , &y1 , &x2 , &y2);

            y[k] = y1;

            s[k++] = Seg(y1 , y2 , x1 , 1);

            y[k] = y2;

            s[k++] = Seg(y1 , y2 , x2 , -1);

        }

        sort(y , y+k);

        sort(s , s+k);

        //去重

        int t = unique(y , y+k) - y;

        double ans = 0;

        for(int i = 0 ; i<k ; i++){

            int pos1 = bin_search(s[i].y1 , t);

            int pos2 = bin_search(s[i].y2 , t) - 1;

           // cout<<"pos: "<<pos1<<" "<<pos2<<endl;

            update(1 , 0 , t-1 , pos1 , pos2 , s[i].d);

            ans += sum[1] * (s[i+1].x - s[i].x);

          //  cout<<"ans: "<<ans<<" "<<sum[1]<<" "<<s[i+1].x - s[i].x<<endl;

        }

        printf("%.2lf\n" , ans);

    }

    return 0;

}
View Code

 参考别人的 400+ms的代码

利用sum[]保存覆盖一次的长度 , ss[]保存覆盖两次及以上的长度

/*

矩形的合并面积

当覆盖次数大于1时才将面积添入

*/

#include <cstdio>

#include <cstring>

#include <iostream>

#include <algorithm>

using namespace std;

const int N = 2010;

#define ls o<<1

#define rs o<<1|1



struct Seg{

    double y1 , y2 , x ;

    int d;

    Seg(){}

    Seg(double y1 , double y2 , double x , int d):y1(y1),y2(y2),x(x),d(d){}

    bool operator<(const Seg &m)const{

        return x < m.x;

    }

}s[N];



int flag[N<<2];

double y[N] , sum[N<<2] , ss[N<<2];

//sum[]表示覆盖一次的总面积,ss表示覆盖两次以上的总面积

void push_up(int o , int l , int r)

{

    if(flag[o]) sum[o] = y[r+1] - y[l];

    else if(l == r) sum[o] = 0;

    else sum[o] = sum[ls]+sum[rs];



    if(flag[o] > 1) ss[o] = y[r+1] - y[l];

    else if(l == r) ss[o] = 0;

    /*因为这题目里的update机制是找到对应边,flag不继续往下传递

    那么如果父节点有一次覆盖,那么这个覆盖不会进入叶子

    那么只要看叶子额外被覆盖的面积,就可以作为父节点覆盖两次以上的面积

    */

    else if(flag[o]) ss[o] = sum[ls] + sum[rs];

    /*

    同上理,如果父节点一次覆盖都没有,子节点覆盖一次也不够,

    所以得看子节点覆盖两次以上的面积

    */

    else ss[o] = ss[ls] + ss[rs];

}



void update(int o , int l , int r , int s , int t , int d)

{

    int m = (l+r) >> 1;

    if(l >= s && r <= t){

        flag[o] += d;

        push_up(o , l , r);

        return;

    }

    if(l == r) return;

    if(m >= s) update(ls , l , m , s , t , d);

    if(m < t) update(rs , m+1 , r , s , t , d);

    push_up(o , l , r);

}



int bin_search(double key , int n)

{

    int l = 0 , r = n - 1;

    while(l <= r){

        int m = (l+r) >> 1;

        if(y[m] == key) return m;

        else if(y[m] < key) l = m+1;

        else r = m-1;

    }

}



int main()

{

  //  freopen("a.in" , "r" , stdin);

    int T;

    double x1 , y1 , x2 , y2;

    scanf("%d" , &T);

    while(T--){

        int n , k=0;

        scanf("%d" , &n);

        for(int i = 0 ; i < n ; i++)

        {

            scanf("%lf%lf%lf%lf" , &x1 , &y1 , &x2 , &y2);

            y[k] = y1;

            s[k++] = Seg(y1 , y2 , x1 , 1);

            y[k] = y2;

            s[k++] = Seg(y1 , y2 , x2 , -1);

        }

        sort(y , y+k);

        sort(s , s+k);

        //去重

        int t = unique(y , y+k) - y;

        double ans = 0;

        for(int i = 0 ; i<k ; i++){

            int pos1 = bin_search(s[i].y1 , t);

            int pos2 = bin_search(s[i].y2 , t) - 1;

           // cout<<"pos: "<<pos1<<" "<<pos2<<endl;

            update(1 , 0 , t-1 , pos1 , pos2 , s[i].d);

            ans += ss[1] * (s[i+1].x - s[i].x);

          //  cout<<"ans: "<<ans<<" "<<sum[1]<<" "<<s[i+1].x - s[i].x<<endl;

        }

        printf("%.2lf\n" , ans);

    }

    return 0;

}
View Code

 (11)HDU 1698 Just A Hook

区间操作简单题

区间的直接赋值更新 , 查询所有区间的总值,利用lazy标记下传即可

#include <cstdio>

#include <cstring>



using namespace std;

const int N = 100005;



struct Tree{

    int l , r , sum , to;//to作为向下传递的lazy标记

}tree[N<<2];



void build(int o , int l , int r)

{

    int m = (l + r) / 2 , ls = o << 1 , rs = o<<1 | 1;

    tree[o].l = l , tree[o].r = r;

    tree[o].sum = r-l+1;

    tree[o].to = 0;

    if(l == r) return ;

    build(ls , l , m);

    build(rs , m+1 , r);

}



void push_down(int o)

{

    int ls = o << 1 , rs = o << 1 | 1;

    if(tree[o].to){

        tree[ls].sum = tree[o].to * (tree[ls].r - tree[ls].l + 1);

        tree[rs].sum = tree[o].to * (tree[rs].r - tree[rs].l + 1);

        tree[ls].to = tree[rs].to = tree[o].to;

        tree[o].to = 0;

    }

}



void update(int o , int l , int r , int v)

{

    if(tree[o].l >= l && tree[o].r <= r){

        tree[o].sum = v*(tree[o].r-tree[o].l+1);

        tree[o].to = v;

        return ;

    }

    push_down(o);

    int ls = o<<1 , rs = o<<1|1;

    int m = (tree[o].l + tree[o].r) >> 1;

    if(m>=l) update(ls , l , r , v);

    if(m+1 <= r) update(rs , l , r , v);

    tree[o].sum = tree[ls].sum + tree[rs].sum;

}



int main()

{

  //  freopen("a.in" , "r" , stdin);

    int cas = 0;

    int T;

    scanf("%d" , &T);

    while(T--){

        int n;

        scanf("%d" , &n);

        build(1 , 1 , n);

        int q , a , b , c;

        scanf("%d" , &q);

        for(int i = 0 ; i<q ; i++){

            scanf("%d%d%d" , &a , &b , &c);

            update(1 , a , b , c);

        }

        printf("Case %d: The total value of the hook is %d.\n" , ++cas , tree[1].sum);

    }

    return 0 ;

}
View Code

 

*(12) POJ 3667 Hotel

区间合并问题

1询问是否有连续 n 个房间入住,如果没有输出0 , 有则输出第一个房间,并入住进行房间更新

2更新 将 s ~s+t-1位置的房间全部清空,可供人入住

题目本身比较简单,但是对于询问操作我确实写的拙计了!!

/* 线段树,区间的合并 找连续区间 */ #include <cstdio> #include <cstring> #include <iostream> #define ls o<<1 #define rs o<<1|1 using namespace std; const int N = 50005; struct Tree{ int l , r , len , ml , mr , ma; }tree[N<<2]; int to[N<<2]; //lazy标志 void _swap(int &a , int &b) { int tmp = a; a = b , b = tmp; } void push_up(int o) { tree[o].ma = max(tree[ls].ma , tree[rs].ma); tree[o].ma = max(tree[o].ma , tree[ls].mr + tree[rs].ml); tree[o].ml = tree[ls].ml , tree[o].mr = tree[rs].mr; if(tree[ls].ml == tree[ls].len) tree[o].ml = tree[ls].ml + tree[rs].ml ; if(tree[rs].mr == tree[rs].len) tree[o].mr = tree[ls].mr + tree[rs].mr ; } //更新连续区间 void UpdateCur(int o , int v) { tree[o].ml = (v^1)*tree[o].len; tree[o].mr = (v^1)*tree[o].len; tree[o].ma = (v^1)*tree[o].len; } void push_down(int o , int l , int r) { if(to[o] >= 0){ to[ls] = to[rs] = to[o]; UpdateCur(ls , to[o]); UpdateCur(rs , to[o]); to[o] = -1; } } void build(int o , int l , int r) { tree[o].l = l , tree[o].r = r , tree[o].len = r-l+1; tree[o].ml = tree[o].mr = tree[o].ma = r-l+1; to[o] = -1; int m = (l+r) >> 1; if(l == r) return; build(ls , l , m); build(rs , m+1 , r); } void update(int o , int l , int r , int s , int t , int v) { int m = (l + r) >> 1; if(l >= s && r <= t){ to[o] = v; UpdateCur(o , v); return; } push_down(o , l , r); if(m >= s) update(ls , l , m , s , t , v); if(m < t) update(rs , m+1 , r , s , t , v); push_up(o); } int query(int o , int l , int r , int v) { if(tree[o].ma < v) return 0; if(l == r) return l; push_down(o , l , r); int m = (l+r) >> 1; if(tree[ls].ma >= v) return query(ls , l , m , v); else if(tree[ls].mr + tree[rs].ml >= v) return m - tree[ls].mr + 1; else return query(rs , m+1 , r , v); } int main() { //freopen("a.in" , "r" , stdin); int n , m , op , s , t , v; while(scanf("%d%d" , &n , &m) == 2) { build(1 , 1 , n); for(int i = 0 ; i<m ; i++){ scanf("%d" , &op); if(op == 2){ scanf("%d%d" , &s , &t); update(1 , 1 , n , s , t+s-1 , 0); //cout<<tree[1].ma<<endl; }else{ scanf("%d" , &v); int ans = query(1 , 1 , n , v); printf("%d\n" , ans); if(ans) update(1 , 1 , n , ans , ans+v-1 , 1); } } } return 0; }
View Code

 (13) HDU3308 最长连续上升子序列

区间合并简单题,相对于Hotel那道题目来说,我们还需要多维护一个 节点最左边的值 , 和一个节点最右边的值

那么合并的时候只有tree[rs].Min > tree[ls].Max (也就是右子树最左边大于左子树最右边,才能合并形成一个新的

连续上升子序列)

  1 /*

  2 线段树,区间的合并

  3 找连续区间

  4 */

  5 #include <cstdio>

  6 #include <cstring>

  7 #include <iostream>

  8 #define ls o<<1

  9 #define rs o<<1|1

 10 

 11 using namespace std;

 12 const int N = 100005;

 13 

 14 struct Tree{

 15     int l , r , len , ml , mr , ma , Min , Max;

 16     /*

 17     Min表示当前节点所包含的最左端的数的数值

 18     Max表示当前节点所包含的最右端的数的数值

 19     */

 20 }tree[N<<2];

 21 int num[N];

 22 

 23 void push_up(int o)

 24 {

 25     tree[o].ma = max(tree[ls].ma , tree[rs].ma);

 26     tree[o].ml = tree[ls].ml , tree[o].mr = tree[rs].mr;

 27     if(tree[rs].Min > tree[ls].Max)

 28     {

 29         tree[o].ma = max(tree[o].ma , tree[ls].mr + tree[rs].ml);

 30 

 31         if(tree[ls].ml == tree[ls].len)

 32             tree[o].ml = tree[ls].ml + tree[rs].ml ;

 33         if(tree[rs].mr == tree[rs].len)

 34             tree[o].mr = tree[ls].mr + tree[rs].mr ;

 35     }

 36 

 37     tree[o].Min = tree[ls].Min , tree[o].Max = tree[rs].Max;

 38 }

 39 

 40 void build(int o , int l , int r)

 41 {

 42     tree[o].l = l , tree[o].r = r , tree[o].len = r-l+1;

 43     int m = (l+r) >> 1;

 44     if(l == r){

 45         tree[o].Max = tree[o].Min = num[l];

 46         tree[o].ma = tree[o].ml = tree[o].mr = 1;

 47         return;

 48     }

 49     build(ls , l , m);

 50     build(rs , m+1 , r);

 51     push_up(o);

 52 }

 53 

 54 void update(int o , int l , int r , int i , int v)

 55 {

 56     int m = (l+r) >> 1;

 57     if(l == r && r == i){

 58         tree[o].Max = tree[o].Min = v;

 59         return;

 60     }

 61     if(m >= i) update(ls , l , m , i , v);

 62     else update(rs , m+1 , r , i , v);

 63     push_up(o);

 64 }

 65 

 66 int query(int o , int l , int r , int s , int t)

 67 {

 68     if(l >= s && r <= t)

 69         return tree[o].ma;

 70     int m = (l+r) >> 1;

 71     int ans = 0;

 72     if(m >= s) ans = max(ans , query(ls , l , m , s , t));

 73     if(m < t) ans = max(ans , query(rs , m+1 , r , s , t));

 74     if(tree[rs].Min > tree[ls].Max)

 75         ans = max(ans , min(tree[ls].mr , m-s+1) + min(tree[rs].ml , t-m));

 76     return ans;

 77 }

 78 

 79 int main()

 80 {

 81   //  freopen("a.in" , "r" , stdin);

 82     int T , n , m , s , t;

 83     char str[5];

 84     scanf("%d" , &T);

 85     while(T--)

 86     {

 87         scanf("%d%d" , &n , &m);

 88         for(int i = 1 ; i<=n ; i++)

 89             scanf("%d" , num+i);

 90 

 91         build(1 , 1 , n);

 92 

 93         for(int i =0 ; i<m ; i++){

 94             scanf("%s%d%d" , str , &s , &t);

 95             if(str[0] == 'U'){

 96                 update(1 , 1 , n , s+1 , t);

 97             }else{

 98                 printf("%d\n" , query(1 , 1 , n , s+1 , t+1));

 99             }

100         }

101     }

102     return 0;

103 }
View Code

 (14)HDU 1540 Tunnel Warfare

区间合并简单题

找对应点所在的最长连续区间的长度

更新操作有毁掉一个点的位置和复原上一个毁掉的点的位置

  1 #include <cstdio>

  2 #include <cstring>

  3 #include <iostream>

  4 

  5 using namespace std;

  6 const int N = 50005;

  7 

  8 struct Node{

  9     int l , r , ml , mr , ma; //ml左最长,mr右最长,ma总最长

 10 }tree[N<<2];

 11 

 12 void build(int o , int l , int r)

 13 {

 14     tree[o].l = l;

 15     tree[o].r = r;

 16     tree[o].ml = r - l + 1;

 17     tree[o].mr = r - l + 1;

 18     tree[o].ma = r - l + 1;

 19     int m = (l + r) / 2;

 20     if(l == r) return ;

 21     build(o<<1 , l , m);

 22     build(o<<1|1 , m+1 , r);

 23 }

 24 

 25 void push_up(int o)

 26 {

 27     int ls = o<<1 , rs = o<<1|1;

 28     tree[o].ml = tree[ls].ml;

 29     tree[o].mr = tree[rs].mr;

 30 

 31     if(tree[ls].ml == (tree[ls].r - tree[ls].l + 1))

 32         tree[o].ml = tree[ls].ml + tree[rs].ml;

 33 

 34     if(tree[rs].mr == (tree[rs].r - tree[rs].l + 1))

 35         tree[o].mr = tree[ls].mr + tree[rs].mr;

 36 

 37     tree[o].ma = max(tree[ls].mr + tree[rs].ml , tree[rs].ma);

 38     tree[o].ma = max(tree[ls].ma , tree[o].ma);

 39 }

 40 

 41 void update(int o , int t , int v)

 42 {

 43     if(tree[o].l == tree[o].r){

 44         if(v == 1) tree[o].ma = tree[o].ml = tree[o].mr = 1;

 45         else tree[o].ma = tree[o].ml = tree[o].mr = 0;

 46         return ;

 47     }

 48     int ls = o<<1 , rs = o<<1|1;

 49     int m = (tree[o].l + tree[o].r) / 2;

 50 

 51     if(t <= m) update(ls , t , v);

 52     else update(rs , t , v);

 53     //要等下层更新完才能递归回去更新上层

 54     push_up(o);

 55 }

 56 

 57 int query(int o , int t)

 58 {

 59     int ls = o<<1 , rs = o<<1|1 , m = (tree[o].l + tree[o].r) / 2;

 60     if(tree[o].l == tree[o].r || tree[o].ma == 0 || tree[o].ma == tree[o].r - tree[o].l + 1)

 61         return tree[o].ma;

 62 

 63     if(t <= m){

 64         if(t >= tree[ls].r - tree[ls].mr + 1)

 65             return query(ls , t) + query(rs , m+1);

 66         else return query(ls , t);

 67     }else{

 68         if(t <= tree[rs].l + tree[rs].ml - 1)

 69             return query(ls , m) + query(rs , t);

 70         else query(rs , t);

 71     }

 72 }

 73 

 74 int que[N] , top;

 75 

 76 int main()

 77 {

 78    // freopen("a.in" , "r" , stdin);

 79     int n,m;

 80     char str[10];

 81     int x;

 82     while(scanf("%d%d",&n,&m)!=EOF){

 83         build(1,1,n);

 84         top=0;

 85         while(m--)

 86         {

 87             scanf("%s",str);

 88             if(str[0]=='D'){

 89                 scanf("%d",&x);

 90                 que[top++]=x;

 91                 update(1,x,0);

 92             }

 93             else if(str[0]=='Q'){

 94                 scanf("%d",&x);

 95                 printf("%d\n",query(1,x));

 96             }

 97             else{

 98                 if(x>0){

 99                     x=que[--top];

100                     update(1,x,1);

101                 }

102             }

103         }

104     }

105     return 0;

106 }
View Code

 (15) HDU 3911

区间修改简单题

区间异或找最长连续1

  1 #include <iostream>

  2 #include <cstdio>

  3 #include <cstring>

  4 using namespace std;

  5 #define N 100005

  6 #define ls o<<1

  7 #define rs o<<1|1

  8 struct Tree{

  9     int l , r , len , ma[2] , ml[2] , mr[2];

 10 }tree[N<<2];

 11 

 12 int num[N] , _xor[N<<2];

 13 

 14 void _swap(int &a , int &b)

 15 {

 16     int temp = a;

 17     a = b ;

 18     b = temp;

 19 }

 20 

 21 void push_up(int o , int l , int r)

 22 {

 23     for(int i=0 ; i<2 ; i++){

 24         tree[o].ma[i] = max(tree[ls].ma[i] , tree[rs].ma[i]);

 25         tree[o].ma[i] = max(tree[o].ma[i] , tree[ls].mr[i] + tree[rs].ml[i]);

 26         tree[o].ml[i] = tree[ls].ml[i] , tree[o].mr[i] = tree[rs].mr[i];

 27         if(tree[ls].ml[i] == tree[ls].len)

 28             tree[o].ml[i] = tree[ls].ml[i]+tree[rs].ml[i];

 29         if(tree[rs].mr[i] == tree[rs].len)

 30             tree[o].mr[i] = tree[ls].mr[i]+tree[rs].mr[i];

 31     }

 32 }

 33 

 34 void CurUpdate(int o , int l , int r)

 35 {

 36     _swap(tree[o].ma[0] , tree[o].ma[1]);

 37     _swap(tree[o].ml[0] , tree[o].ml[1]);

 38     _swap(tree[o].mr[0] , tree[o].mr[1]);

 39 }

 40 

 41 void push_down(int o , int l , int r)

 42 {

 43     if(_xor[o]){

 44         _xor[ls]^=1 , _xor[rs]^=1;

 45         CurUpdate(ls , tree[ls].l , tree[ls].r);

 46         CurUpdate(rs , tree[rs].l , tree[rs].r);

 47         _xor[o] = 0;

 48     }

 49 }

 50 

 51 void build(int o , int l , int r)

 52 {

 53     _xor[o] = 0;

 54     tree[o].len = r-l+1;

 55     tree[o].l = l , tree[o].r = r;

 56     if(l == r){

 57         tree[o].ma[0] = tree[o].ml[0] = tree[o].mr[0] = (num[l]&1)^1;

 58         tree[o].ma[1] = tree[o].ml[1] = tree[o].mr[1] = num[l]&1;

 59         return;

 60     }

 61     int m = (l+r)>>1;

 62     build(ls , l , m);

 63     build(rs , m+1 , r);

 64     push_up(o , l , r);

 65 }

 66 

 67 void update(int o , int s , int t)

 68 {

 69     int l = tree[o].l , r = tree[o].r;

 70     if(l >= s && r <= t){

 71         _xor[o] ^= 1;

 72         CurUpdate(o , l , r);

 73         return;

 74     }

 75     push_down(o , l , r);

 76     int m = (l+r)>>1;

 77     if(m>=s) update(ls , s , t);

 78     if(m<t) update(rs , s , t);

 79     push_up(o , l , r);

 80 }

 81 

 82 int query(int o , int s , int t)

 83 {

 84     int l = tree[o].l , r = tree[o].r;

 85     if(l >= s && r <= t)

 86         return tree[o].ma[1];

 87 

 88     push_down(o , l , r);

 89 

 90     int ans = 0 , m=(l+r)>>1;

 91     if(m >= s){

 92        // ans += max(tree[ls].ma[1] , min(tree[ls].mr[1] , m-s+1) + min(tree[rs].ml[1] , t-m));

 93         ans = max(ans , query(ls , s , t));

 94     }

 95     if(m < t){

 96         ans = max(ans , query(rs , s , t));

 97     }

 98     ans = max(ans , min(tree[ls].mr[1] , m-s+1) + min(tree[rs].ml[1] , t-m));

 99     return ans;

100 }

101 

102 int main()

103 {

104    // freopen("a.in" , "r" , stdin);

105     int n , m , op , s , t ;

106     while(scanf("%d" , &n) == 1){

107         for(int i = 1 ; i<=n ; i++)

108             scanf("%d" , num+i);

109 

110         build(1 , 1 , n);

111 

112         scanf("%d" , &m);

113         for(int i = 0 ; i<m ; i++){

114             scanf("%d%d%d" , &op , &s , &t);

115             if(op)

116                 update(1 , s , t);

117             else printf("%d\n" , query(1 , s , t));

118         }

119     }

120     return 0;

121 }
View Code

 *(16) HDU 4521

题目大意:

n个数 ,n<=10^5 , 每个数不大于 10^5  , 找到一个间距大于d 最长上升子序列

数字个数达到10^5,无法DP

采用线段树

单点更新,关键在于建立线段树模型,在线段树的每一个节点都维护一个以当前节点结尾所能达到的最大值

向上更新时就是mx[o] = max(max[o<<1] , max[o<<1|1])

表示o所包含的子节点中取某一个结尾所能得到的上升子序列的最大长度

 1 /*

 2 dp[i] 保存以第i个数字结尾

 3 所能得到的最大长度

 4 因为a[i] <= 10^5

 5 线段树上保存的以0 ~ 最大的a[i]

 6 维护以某个点结尾所能得到的最大

 7 长度

 8 为了保证当前dp值得获取正确

 9 也就是只在线段树上保存更新

10 i-d-1前的所有数据

11 这样最后询问得到的必然是能

12 够相隔d个间距所衔接的数据

13 */

14 #include <cstdio>

15 #include <cstring>

16 #include <iostream>

17 

18 using namespace std;

19 #define ls o<<1,l,m

20 #define rs o<<1|1,m+1,r

21 const int N = 100005;

22 int mx[N<<2] , dp[N] , a[N];

23 

24 void push_up(int o)

25 {

26     mx[o] = max(mx[o<<1] , mx[o<<1|1]);

27 }

28 

29 void update(int o , int l , int r , int pos , int v)

30 {

31     if(l == r){

32         mx[o] = max(v , mx[o]);

33         return;

34     }

35     int m= (l+r)>>1;

36     if(m >= pos) update(ls , pos , v);

37     if(m < pos) update(rs , pos , v);

38     push_up(o);

39     return;

40 }

41 

42 int query(int o , int l , int r , int s , int t)

43 {

44     if(s <= l && t >= r)

45         return mx[o];

46     int m = (l+r) >> 1 , ans = 0;

47     if(m >= s) ans = max(ans , query(ls , s , t));

48     if(m < t) ans = max(ans , query(rs , s , t));

49     return ans;

50 }

51 

52 int main()

53 {

54    // freopen("a.in" , "r" , stdin);

55     int n , d;

56     while(~scanf("%d%d" , &n , &d)){

57         int len = 0;

58         for(int i = 1 ; i<=n ; i++){

59             scanf("%d" , a+i);

60             len = max(len , a[i]);

61         }

62         //初始树上的mx值均为0,所以利用memset函数代替建树过程

63         memset(mx , 0 , sizeof(mx));

64         int ans = 0;

65         for(int i = 1 ; i<=n ; i++){

66             if(i-d-1 >= 1) update(1 , 0 , len , a[i-d-1] , dp[i-d-1]);

67             if(a[i]) dp[i] = query(1 , 0 , len , 0 , a[i]-1) + 1;

68             else dp[i] = 1;

69             ans = max(ans , dp[i]);

70         }

71         printf("%d\n" , ans);

72     }

73     return 0;

74 }
View Code

 (17) POJ 2481

利用排序解决 e 的关系,然后根据 s 放入线段树中进行操作

 1 #include <cstdio>

 2 #include <cstring>

 3 #include <algorithm>

 4 using namespace std;

 5 const int N = 100005;

 6 #define ls o<<1,l,m

 7 #define rs o<<1|1,m+1,r

 8 

 9 int sum[N<<2] , rec[N];

10 

11 struct Cow{

12     int s , e , id;

13     bool operator==(const Cow &m)const{

14         return s == m.s && e == m.e;

15     }

16 }cow[N];

17 

18 bool cmp(const Cow &m1 , const Cow &m2)

19 {

20     if(m1.e == m2.e) return m1.s < m2.s;

21     return m1.e > m2.e;

22 }

23 

24 void update(int o , int l , int r , int v)

25 {

26     if(l == r && l == v){

27         sum[o]++;

28         return ;

29     }

30     if(l == r) return ;

31     int m = (l+r) >> 1;

32     if(m >= v) update(ls , v);

33     else update(rs , v);

34     sum[o] = sum[o<<1] + sum[o<<1|1];

35 }

36 

37 int query(int o , int l , int r , int s , int t)

38 {

39     if(s <= l && t >= r)

40         return sum[o];

41     int m=(l+r) >> 1 , ans = 0;

42     if(m >= s) ans += query(ls , s , t);

43     if(m < t) ans += query(rs , s , t);

44     return ans;

45 }

46 

47 int main()

48 {

49    // freopen("a.in" , "r" , stdin);

50     int n;

51     while(scanf("%d" , &n) , n){

52         for(int i=1 ; i<=n ; i++){

53             scanf("%d%d" , &cow[i].s , &cow[i].e);

54             cow[i].id = i;

55         }

56         //按e由大到小排序

57         sort(cow+1 , cow+n+1 , cmp);

58 

59         memset(sum  , 0 , sizeof(sum));

60 

61         rec[cow[1].id] = 0;

62         update(1 , 0 , N , cow[1].s);

63         for(int i=2 ; i<=n ; i++){

64          //   printf("%d : %d %d\n" , i , cow[i].s , cow[i].e);

65             if(cow[i] == cow[i-1])

66                 rec[cow[i].id] = rec[cow[i-1].id];

67             else

68                 rec[cow[i].id] = query(1 , 0 , N , 0 , cow[i].s);

69             update(1 , 0 , N , cow[i].s);

70         }

71         for(int i = 1 ; i<n ; i++)

72             printf("%d " , rec[i]);

73         printf("%d\n" , rec[n]);

74     }

75     return 0;

76 }
View Code

 *(18)ZOJ 3511

对一个凸多边形的蛋糕进行m次切割,没次切割内部不相交,但是切割端点可相交,问最后切割后边数最多的蛋糕的边数(切割出来的边也不能忘记记录到总的边中)

这道题目本身不难,但是思路很奇特

因为切割内部不相交,所以每次切的时候包含点少的切割先进行切割,必然保证不影响后面的切割,所以根据y-x排序,这里y始终大于x

然后我们每次切可以知道除了两个端点,其余x+1~y-1 这些点是不会被后面的切割后取走放入当前蛋糕的,所以线段树以1~n个点建立,每次访问一次切割,将当前x+1~y-1

个点赋给当前边,那么产生的蛋糕就是这个区间内未被取走的点+2便是边的数量

用sum[i]表示未曾被取走的点,简单维护一个sum即可

#include <cstdio>

#include <cstring>

#include <iostream>

#include <algorithm>

using namespace std;

#define ls o<<1

#define rs o<<1|1

#define define_m int m=(l+r)>>1

#define N 10010



struct Cut{

    int x , y;

    bool operator<(const Cut &m)const{

        if(y-x == m.y-m.x) return x<m.x;

        return y-x < m.y-m.x;

    }

}cut[N];



int sum[N<<2];



void push_up(int o)

{

    sum[o] = sum[ls]+sum[rs];

}



void build(int o , int l , int r)

{

    if(l==r) sum[o]=1;

    else{

        define_m;

        build(ls , l , m);

        build(rs , m+1 , r);

        push_up(o);

    }

}



void update(int o , int l , int r , int s , int t)

{

    if(l>r) return;

    if(l>=s && r<=t){

       // cout<<"o: "<<o<<" l: "<<l<<" r: "<<r<<endl;

        sum[o]=0;

        return ;

    }

    define_m;

    if(m>=s) update(ls , l , m , s , t);

    if(m<t) update(rs , m+1 , r , s , t);

    push_up(o);

}



int main()

{

 //   freopen("a.in" , "r" , stdin);

    int n,m;

    while(~scanf("%d%d" , &n , &m))

    {

        build(1 , 1 , n);

        for(int i=0 ; i<m ; i++){

            scanf("%d%d" , &cut[i].x , &cut[i].y);

            if(cut[i].x>cut[i].y) swap(cut[i].x , cut[i].y);

        }

        sort(cut , cut+m);

        int ans = 0 , pre = sum[1];

        for(int i=0 ; i<m ; i++){

            update(1 , 1 , n , cut[i].x+1 , cut[i].y-1);

          //  cout<<"there: "<<pre<<" "<<sum[1]<<" "<<cut[i].x+1<<" "<<cut[i].y-1<<endl;

            ans = max(ans , pre-sum[1]+2);

            pre=sum[1];

        }

        ans = max(ans , sum[1]);

        printf("%d\n" , ans);

    }

    return 0;

}
View Code

 (19)SPOJ GSS1 静态区间求解最大子段和

 题目大意:

给定n个数,再给q个区间询问,希望在区间s,t中找到一段连续的子序列使其和最大

 

因为询问上万,节点数50000,明显是用线段树去做,这里很明显的区间更新,唯一写起来有点恶心的是询问

每一个区间的最大都要跟左右区间的左最大右最大有关系

反正时要注意细节了,查询的时候同时要查询其左右连续最大

 1 #include <cstdio>

 2 #include <cstring>

 3 #include <iostream>

 4 #include <algorithm>

 5 #include <set>

 6 #include <map>

 7 using namespace std;

 8 

 9 const int INF = 0x3fffffff;

10 #define N 50010

11 #define MOD 100007

12 #define ls o<<1

13 #define rs o<<1|1

14 #define define_m int m=(l+r)>>1

15 

16 int ml[N*3] , mr[N*3] , mx[N*3] , sum[N*3];

17 int a[N] , pre[N] , n;

18 

19 void push_up(int o)

20 {

21     mx[o] = max(mx[ls] , mx[rs]);

22     mx[o] = max(mx[o] , ml[rs]+mr[ls]);

23     ml[o] = max(ml[ls],sum[ls]+ml[rs]) , mr[o] = max(mr[rs],sum[rs]+mr[ls]);

24     sum[o] = sum[ls]+sum[rs];

25 }

26 

27 void build(int o , int l , int r)

28 {

29     if(l==r){

30         sum[o]=ml[o]=mr[o]=mx[o]=a[l];

31         return;

32     }

33     define_m;

34     build(ls , l , m);

35     build(rs , m+1 , r);

36     push_up(o);

37 }

38 

39 void query(int o , int l , int r , int s , int t , int &ansl , int &ansr , int &ans)

40 {

41     if(l>=s && r<=t){

42         ans = mx[o];

43         ansl = ml[o];

44         ansr = mr[o];

45         return ;

46     }

47     define_m;

48     if(m>=t) query(ls , l , m , s , t , ansl , ansr , ans);

49     else if(m<s) query(rs , m+1 , r , s , t , ansl , ansr ,ans);

50     else{

51         int t1,t2,t3,t4,t5,t6;

52         query(ls , l , m , s , m , t1 , t2 , t3);

53         query(rs , m+1 , r , m+1 , t , t4 , t5 , t6);

54         ansl = max(t1 , pre[m]-pre[s-1]+t4) , ansr = max(t5 , pre[t]-pre[m]+t2);

55         ans = max(t3 , t6);

56         ans = max(ans , t2+t4);

57     }

58     //cout<<o<<" "<<l<<" "<<r<<" "<<s<<" "<<t<<" "<<ansl<<" "<<ansr<<" "<<ans<<endl;

59 }

60 

61 int main()

62 {

63     #ifndef ONLINE_JUDGE

64         freopen("a.in" , "r" , stdin);

65     #endif // ONLINE_JUDGE

66     while(~scanf("%d" , &n))

67     {

68         for(int i=1 ; i<=n ; i++){

69             scanf("%d" , a+i);

70             pre[i] = pre[i-1]+a[i];

71         }

72         build(1 , 1 , n);

73         int m;

74         scanf("%d" , &m);

75         for(int i=0 ; i<m ; i++){

76             int s , t;

77             scanf("%d%d" , &s , &t);

78             int t1,t2,t3;

79             query(1,1,n,s,t,t1,t2,t3);

80             printf("%d\n" , t3);

81         }

82     }

83     return 0;

84 }
View Code

 

你可能感兴趣的:(线段树)