转载自:http://www.notonlysuccess.com/
不可不看的经典 学线段树必看,大牛很多,给后人留下记录的却没有几个,谢谢这位大牛~!
因为我这最近他博客打不开了。。。特意从别人那找来转帖贴下,大家分享下~!
粘贴的比较粗糙,原来的那些个连接都没了,大家凑合下~
【完全版】线段树
很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文章了,觉得当时的代码风格实在是太丑了,很多线段树的初学者可能就是看着这篇文章来练习的,如果不小心被我培养出了这么糟糕的风格,实在是过意不去,正好过几天又要给集训队讲解线段树,所以决定把这些题目重新写一遍,顺便把近年我接触到的一些新题更新上去~;并且学习了splay等更高级的数据结构后对线段树的体会有更深了一层,线段树的写法也就比以前飘逸,简洁且方便多了.
在代码前先介绍一些我的线段树风格:
maxn是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于maxn的最小2x的两倍
lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示
以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson和rson的预定义可以很方便
PushUP(int rt)是把当前结点的信息更新到父结点
PushDown(int rt)是把当前结点的信息更新给儿子结点
rt表示当前子树的根(root),也就是当前所在的结点
整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分:
单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来
hdu1166 敌兵布阵
题意:O(-1)
思路:O(-1)
线段树功能:update:单点增减 query:区间求和
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define lson l,m,rt<<1 5 #define rson m+1,r,rt<<1|1 6 const int maxn=55555; 7 int sum[maxn << 2]; 8 void PushUP(int rt) 9 { 10 sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 11 } 12 void build(int l,int r,int rt) 13 { 14 if(l == r) 15 { 16 scanf("%d",&sum[rt]); 17 return; 18 } 19 int m= (l + r) >> 1; 20 build(lson); 21 build(rson); 22 PushUP(rt); 23 } 24 void update(int p, int data,int l,int r,int rt) 25 { 26 if(l == r) 27 { 28 sum[rt] += data; 29 return ; 30 } 31 int m = (l + r) >> 1; 32 if(p <= m) 33 update(p,data,lson); 34 if(p > m) 35 update(p,data,rson); 36 PushUP(rt); 37 } 38 int query(int L,int R,int l,int r,int rt) 39 { 40 if(L <= l && r <= R) 41 return sum[rt]; 42 int m = (l + r) >> 1; 43 int ret=0; 44 if(L <= m) 45 ret+=query(L,R,lson); 46 if(R > m) 47 ret+=query(L,R,rson); 48 return ret; 49 } 50 int main() 51 { 52 int t,n,m=0,a,b; 53 char ch[15]; 54 scanf("%d",&t); 55 while(t--) 56 { 57 printf("Case %d:\n",++m); 58 scanf("%d",&n); 59 build(1,n,1); 60 while(1) 61 { 62 scanf("%s",ch); 63 if(ch[0]=='E') 64 break; 65 scanf("%d%d",&a,&b); 66 if(ch[0]=='Q') 67 printf("%d\n",query(a,b,1,n,1)); 68 if(ch[0]=='A') 69 update(a,b,1,n,1); 70 if(ch[0]=='S') 71 update(a,-b,1,n,1); 72 } 73 } 74 return 0; 75 }
hdu1754 I Hate It
题意:O(-1)
思路:O(-1)
线段树功能:update:单点替换 query:区间最值
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 #define lson l , m , rt << 1 5 #define rson m+1 , r , rt << 1 | 1 6 const int maxn=222222; 7 int sum[maxn<<2]; 8 void PushUP(int rt) 9 { 10 sum[rt] = max(sum[rt << 1],sum[rt << 1 | 1]); 11 } 12 void build(int l, int r, int rt) 13 { 14 if(l == r) 15 { 16 scanf("%d",&sum[rt]); 17 return ; 18 } 19 int m = (l + r) >> 1; 20 build(lson); 21 build(rson); 22 PushUP(rt); 23 } 24 void update(int p,int data ,int l,int r,int rt) 25 { 26 if(r == l) 27 { 28 sum[rt] = data; 29 return ; 30 } 31 int m = (l + r) >> 1; 32 if(p <= m) 33 update(p,data,lson); 34 else 35 update(p,data,rson); 36 PushUP(rt); 37 } 38 int query(int L,int R,int l,int r,int rt) 39 { 40 if(L <= l && r <= R) 41 return sum[rt]; 42 int m = (l + r) >> 1; 43 int ret = 0; 44 if(L <= m) ret = max(ret ,query(L,R,lson)); 45 if(R > m) ret = max(ret ,query(L,R,rson)); 46 return ret; 47 } 48 int main() 49 { 50 int n,m,a,b; 51 char ch[3]; 52 while(~scanf("%d%d",&n,&m)) 53 { 54 build(1,n,1); 55 while(m--){ 56 scanf("%s",ch); 57 scanf("%d%d",&a,&b); 58 if(ch[0]=='Q') printf("%d\n",query(a,b,1,n,1)); 59 else update(a,b,1,n,1); 60 } 61 } 62 }
hdu1394 Minimum Inversion Number
题意:求Inversion后的最小逆序数
思路:用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解
线段树功能:update:单点增减 query:区间求和
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 #define lson l , m ,rt << 1 5 #define rson m+1 , r , rt << 1 | 1 6 const int maxn = 5555; 7 int pos[maxn << 2]; 8 void PushUP(int rt) 9 { 10 pos[rt] = pos[rt << 1] + pos[rt << 1 | 1]; 11 } 12 void build(int l,int r,int rt) 13 { 14 if(l == r) 15 { 16 pos[rt] = 0; 17 return; 18 } 19 int m = (l + r) >> 1; 20 build(lson); 21 build(rson); 22 PushUP(rt); 23 } 24 void update(int p,int l,int r,int rt)//目测这里的update 25 //是把p排到的位置 26 //加一而得到的结果 27 { 28 if(l == r) 29 { 30 pos[rt]++; 31 return; 32 } 33 int m=(l + r) >> 1; 34 if(p <= m) 35 update(p , lson); 36 else 37 update(p , rson); 38 PushUP(rt); 39 } 40 int query(int L,int R,int l,int r,int rt)//知道了他的设计意图 41 { //第一次出现的时候吧 42 if(L <= l && r <= R) //把包含他一只直到最后 43 { //出现的信息都是逆序数的个数 44 return pos[rt]; 45 } 46 int m = (l + r) >> 1; 47 int ret = 0; 48 if(L <= m) 49 ret += query(L,R,lson); 50 if(R > m) 51 ret += query(L,R,rson); 52 return ret; 53 } 54 int x[maxn]; 55 int main() 56 { 57 int n,i; 58 while(~scanf("%d",&n)) 59 { 60 build(1 , n , 1); 61 int sum = 0; 62 for(i=1;i<=n;i++) 63 { 64 scanf("%d",&x[i]); 65 sum += query( x[i]+1 , n , 1 , n , 1); 66 update( x[i]+1 , 1 , n , 1 ); 67 } 68 int ret = sum;//此时的ret是第一种情况的结果 69 for(i = 1 ; i <= n ;++ i) 70 { 71 sum += n - x[i] - x[i] - 1; 72 ret = min( ret , sum ); 73 } 74 printf("%d\n",ret); 75 } 76 return 0; 77 }
hdu2795 Billboard
题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子
思路:每次找到最大值的位子,然后减去L
线段树功能:query:区间求最大值的位子(直接把update的操作在query里做了)
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define lson l , m , rt << 1 5 #define rson m+1 , r , rt <<1 | 1 6 int h , w , n; 7 const int maxn=222222; 8 int MAX[maxn << 2]; 9 void PushUP(int rt) 10 { 11 MAX[rt] = max( MAX[rt << 1] , MAX[rt << 1 | 1]); 12 } 13 void build(int l , int r , int rt) 14 { 15 MAX[rt] = w; 16 if(l == r) 17 return ; 18 int m = (l + r) >> 1; 19 build(lson); 20 build(rson); 21 } 22 int query(int x,int l,int r,int rt) 23 { 24 if(l == r) 25 { 26 MAX[ rt ] -= x; 27 return l; 28 } 29 int m=(l + r) >> 1; 30 int ret = (MAX[rt << 1] >= x) ? query( x , lson ) : query( x , rson ); 31 PushUP( rt ); 32 return ret; 33 } 34 int main() 35 { 36 while(~scanf("%d%d%d",&h,&w,&n)) 37 { 38 if(h > n) 39 h = n; 40 build(1 , h , 1); 41 while(n--) 42 { 43 int x; 44 scanf("%d",&x); 45 if( MAX[1] < x ) 46 printf("-1\n"); 47 else 48 printf("%d\n",query(x,1,h,1)); 49 } 50 } 51 return 0; 52 }
练习:
poj2828 Buy Tickets
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define lson l, m , rt << 1 5 #define rson m+1 , r , rt << 1 | 1 6 const int maxn=222222; 7 int pos[maxn << 2]; 8 int A[200005][2]; 9 int ans[200005]; 10 int id; 11 void build(int l , int r ,int rt) 12 { 13 pos[rt] = r - l +1; 14 if(l == r) 15 return; 16 int m=(l + r) >> 1; 17 build(lson); 18 build(rson);//建立树的过程 19 } 20 void update(int p,int l,int r,int rt) 21 { 22 pos[rt]--;//首先进入首先判断,整个的空余位置一定时间少的 23 if(l == r)//判断的区间唯一时;只有这一种情况了 24 { 25 id=l; 26 return; 27 } 28 int m = (l + r) >> 1;//二分区间进行判断 29 if( pos[rt << 1] >= p )//坐区间空格数大于已知空格数,定在左边 30 update( p , lson );//进入坐区间 31 else 32 { 33 p -= pos[rt << 1];//不进坐区间就进右区间同时空格数减少坐区间空格的数目; 34 update(p , rson ); 35 } 36 } 37 int main() 38 { 39 int n; 40 while(~scanf("%d",&n)) 41 { 42 build(1 , n , 1); 43 for(int i = 1 ; i <= n ; i ++) 44 scanf("%d%d",&A[i][0],&A[i][1]); 45 for(int i = n ; i >= 1;i --) 46 { 47 update(A[i][0]+1 , 1 , n , 1); 48 ans[id] = A[i][1]; 49 } 50 for(int i = 1;i <= n;i ++) 51 printf("%d%c",ans[i],i==n?'\n':' '); 52 } 53 return 0; 54 }
poj2886 Who Gets the Most Candies?
1 #include<cstdio> 2 #include<cmath> 3 #include<algorithm> 4 using namespace std; 5 #define lson l , m , rt << 1 6 #define rson m+1 , r , rt << 1 | 1 7 const int maxn=555555; 8 int tree[maxn << 2]; 9 const int antiprime[]={1,2,4,6,12,24,36,48,60,120,180,240,360,720,840, 10 1260,1680,2520,5040,7560,10080,15120,20160,25200,27720, 11 45360,50400,55440,83160,110880,166320,221760,277200, 12 332640,498960,554400,665280};//1到665280的所有反素数 13 const int factorNum[]={1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,60,64,72,80,84, 14 90,96,100,108,120,128,144,160,168,180,192,200,216,224};//所反素数包含的约数的个数 15 struct child 16 { 17 char name[15]; 18 int val; 19 }c[maxn]; 20 void build(int l ,int r ,int rt) 21 { 22 tree[rt]=r - l + 1 ; 23 if(r == l) return; 24 int m=(l + r) >> 1; 25 build(lson); 26 build(rson);//这个建树很熟悉,记录各个子树所含的因子 27 } 28 int update(int p,int l,int r,int rt) 29 { 30 tree[rt]--; 31 if(l == r) 32 return l; 33 int m= (l + r) >> 1; 34 if(p <= tree[rt<<1])//如果 35 return update(p,lson); 36 else 37 return update(p-tree[rt << 1],rson); 38 } 39 int main() 40 { 41 int n,k,&mod=tree[1]; 42 while(~scanf("%d%d",&n,&k)) 43 { 44 for(int i=1;i<=n;++i) 45 scanf("%s%d",&c[i].name,&c[i].val); 46 build( 1, n ,1); 47 int cnt = 0; 48 while(cnt < 35 && antiprime[cnt] <= n) 49 cnt++;//找出不大于n的最小反素数;第n个数,下表减一 50 cnt--;//记录最小反素数的约数的个数的位置;最大的值已然确定 51 int pos=0; 52 c[pos].val=0; 53 for(int i=0;i<antiprime[cnt];++i) 54 { 55 if(c[pos].val > 0) 56 k = ((k-1-1+c[pos].val)%mod+mod)%mod+1; 57 else k=((k-1+c[pos].val)%mod+mod)%mod+1; 58 pos=update(k,1,n,1);//找到它并,抹去 59 //只一个位置的数据,其实就是用线段树模拟约瑟夫环; 60 } 61 printf("%s %d\n",c[pos].name,factorNum[cnt]); 62 } 63 return 0; 64 }
成段更新(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候
hdu1698 Just a Hook
题意:O(-1)
思路:O(-1)
线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息)
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 #define lson l, m, rt << 1 5 #define rson m+1, r, rt << 1 | 1 6 const int maxn = 111111; 7 int tree[maxn << 2],col[maxn << 2]; 8 int t , n ; 9 void build(int l,int r,int rt) 10 { 11 tree[rt] = r - l +1; 12 col[rt] = 0; 13 if(l == r) 14 return; 15 int m = (l + r) >> 1; 16 build(lson); 17 build(rson); 18 } 19 void PushUP(int rt) 20 { 21 tree[rt] = tree[rt << 1] + tree[rt << 1 | 1]; 22 } 23 void PushDown(int rt,int m) 24 { 25 if(col[rt]) 26 { 27 col[rt << 1] = col[rt << 1 | 1] = col[rt]; 28 tree[rt << 1] = (m - (m >> 1)) * col[rt]; 29 tree[rt << 1 | 1]=(m >> 1) * col [rt]; 30 col[rt] = 0; 31 } 32 } 33 void update(int L,int R,int ren,int l,int r,int rt) 34 { 35 if(L <= l && r <= R) 36 { 37 col[rt] = ren; 38 tree[rt]= ( r - l +1 ) * ren; 39 return ; 40 } 41 PushDown(rt,r-l+1); 42 int m = (l + r) >> 1; 43 if(L <= m) 44 update(L, R, ren,lson); 45 if(R > m) 46 update(L, R, ren,rson); 47 PushUP(rt); 48 } 49 int main() 50 { 51 int i=0,q,a,b,c; 52 scanf("%d",&t); 53 while(t--) 54 { 55 scanf("%d",&n); 56 build(1, n, 1); 57 scanf("%d",&q); 58 while(q--) 59 { 60 scanf("%d%d%d",&a,&b,&c); 61 update(a,b,c,1,n,1); 62 } 63 printf("Case %d: The total value of the hook is %d.\n",++i,tree[1]); 64 } 65 return 0; 66 }
poj3468 A Simple Problem with Integers
题意:O(-1)
思路:O(-1)
线段树功能:update:成段增减 query:区间求和
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 #define lson l, m, rt << 1 5 #define rson m+1, r, rt << 1 | 1 6 const int maxn = 111111; 7 long long tree[maxn << 2]; 8 long long col[maxn << 2]; 9 void PushUP(int rt) 10 { 11 tree[rt] = tree[rt << 1] + tree[rt << 1 | 1]; 12 } 13 void build(int l,int r,int rt) 14 { 15 col[rt] = 0; 16 if(l == r) 17 { 18 scanf("%I64d",&tree[rt]); 19 return ; 20 } 21 int m = (l + r) >> 1; 22 build(lson); 23 build(rson); 24 PushUP(rt); 25 } 26 void PushDown(int rt,int m) 27 { 28 if(col[rt]) 29 { 30 col[rt << 1] += col[rt]; 31 col[rt << 1 | 1] += col[rt]; 32 tree[rt << 1] += (m - (m >> 1)) * col[rt]; 33 tree[rt << 1 | 1] += (m >> 1) * col[rt]; 34 col[rt]=0; 35 } 36 } 37 void update(int L,int R,int ren,int l,int r,int rt) 38 { 39 if(L <= l && r <= R) 40 { 41 col[rt] += ren; 42 tree[rt] += (r - l + 1) * ren; 43 return; 44 } 45 PushDown(rt, r - l + 1); 46 int m = (l + r) >> 1; 47 if(L <= m) 48 update(L, R, ren, lson); 49 if(R > m) 50 update(L, R, ren, rson); 51 PushUP(rt); 52 } 53 long long query(int L,int R,int l,int r,int rt) 54 { 55 if(L <= l && r <= R) 56 { 57 return tree[rt]; 58 } 59 PushDown(rt ,r - l +1); 60 int m = (r + l) >> 1; 61 long long ret = 0; 62 if(L <= m) 63 ret += query(L, R, lson); 64 if(R > m) 65 ret += query(L, R, rson); 66 return ret; 67 } 68 int main() 69 { 70 int n,qn,a,b,c; 71 char ch[3]; 72 scanf("%d%d",&n,&qn); 73 build(1, n, 1); 74 while(qn--) 75 { 76 scanf("%s",ch); 77 if(ch[0]=='Q') 78 { 79 scanf("%d%d",&a,&b); 80 printf("%I64d\n",query(a, b, 1, n, 1)); 81 } 82 else if(ch[0]=='C') 83 { 84 scanf("%d%d%d",&a,&b,&c); 85 update(a, b, c, 1, n, 1); 86 } 87 } 88 return 0; 89 }
poj2528 Mayor’s posters
题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报
思路:这题数据范围很大,直接搞超时+超内存,需要离散化:
离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了
所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多
而这题的难点在于每个数字其实表示的是一个单位长度(并且一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj这题数据奇弱)
给出下面两个简单的例子应该能体现普通离散化的缺陷:
1-10 1-4 5-10
1-10 1-4 6-10
为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.
线段树功能:update:成段替换 query:简单hash
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 #define lson l , m , rt << 1 6 #define rson m + 1 , r , rt << 1 | 1 7 const int maxn = 11111; 8 bool hash[maxn]; 9 int li[maxn] , ri[maxn]; 10 int X[maxn*3]; 11 int col[maxn<<4]; 12 int cnt; 13 void PushDown(int rt) 14 { 15 if (col[rt] != -1) 16 { 17 col[rt<<1] = col[rt<<1|1] = col[rt]; 18 col[rt] = -1; 19 } 20 } 21 void update(int L,int R,int c,int l,int r,int rt) 22 { 23 if (L <= l && r <= R) 24 { 25 col[rt] = c; 26 return ; 27 } 28 PushDown(rt); 29 int m = (l + r) >> 1; 30 if (L <= m) update(L , R , c , lson); 31 if (m < R) update(L , R , c , rson); 32 } 33 void query(int l,int r,int rt) 34 { 35 if (col[rt] != -1) 36 { 37 if (!hash[col[rt]]) cnt ++; 38 hash[ col[rt] ] = true; 39 return ; 40 } 41 if (l == r) return ; 42 int m = (l + r) >> 1; 43 query(lson); 44 query(rson); 45 } 46 int Bin(int key,int n,int X[]) 47 { 48 int l = 0 , r = n - 1; 49 while (l <= r) 50 { 51 int m = (l + r) >> 1; 52 if (X[m] == key) return m; 53 if (X[m] < key) l = m + 1; 54 else r = m - 1; 55 } 56 return -1; 57 } 58 int main() 59 { 60 int T , n; 61 scanf("%d",&T); 62 while (T --) 63 { 64 scanf("%d",&n); 65 int nn = 0; 66 for (int i = 0 ; i < n ; i ++) 67 { 68 scanf("%d%d",&li[i] , &ri[i]);//里藏头,日藏尾; 69 X[nn++] = li[i];//X[]记录每一次出现的端点; 70 X[nn++] = ri[i]; 71 } 72 sort(X , X + nn);//对其一次排序好一一对应; 73 // for(int i = 0; i <= nn; i++) 74 // printf("%d\t",X[i]); 75 // printf("\n"); 76 int m = 1; 77 for (int i = 1 ; i < nn; i ++) 78 { 79 if (X[i] != X[i - 1])//若相邻的不相同 80 X[m ++] = X[i];//实着是压缩X[]的数量 81 } 82 // for(int i = 0;i <= m ;i ++) 83 //printf("%d\t",X[i]); 84 //printf("\n"); 85 for (int i = m - 1 ; i > 0 ; i --) 86 { 87 if (X[i] != X[i - 1] + 1)//第一遍压缩以后看相邻的差距是否为一 88 X[m ++] = X[i] + 1;// 89 } 90 sort(X , X + m); 91 memset(col , -1 , sizeof(col)); 92 for (int i = 0 ; i < n ; i ++) 93 { 94 int l = Bin(li[i] , m , X); 95 int r = Bin(ri[i] , m , X); 96 update(l , r , i , 0 , m , 1); 97 } 98 cnt = 0; 99 memset(hash , false , sizeof(hash)); 100 query(0 , m , 1); 101 printf("%d\n",cnt); 102 } 103 return 0; 104 }
poj3225 Help with Intervals
题意:区间操作,交,并,补等
思路:
我们一个一个操作来分析:(用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含)
U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换
成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作
很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了
所以当一个节点得到覆盖标记时把异或标记清空
而当一个节点得到异或标记的时候,先判断覆盖标记,如果是0或1,直接改变一下覆盖标记,不然的话改变异或标记
开区间闭区间只要数字乘以2就可以处理(偶数表示端点,奇数表示两端点间的区间)
线段树功能:update:成段替换,区间异或 query:简单hash
1 #include <cstdio> 2 #include <cstring> 3 #include <cctype> 4 #include <algorithm> 5 using namespace std; 6 #define lson l , m , rt << 1 7 #define rson m + 1 , r , rt << 1 | 1 8 const int maxn = 131072; 9 bool hash[maxn+1]; 10 int cover[maxn<<2]; 11 int XOR[maxn<<2]; 12 void FXOR(int rt) { 13 if (cover[rt] != -1) cover[rt] ^= 1;//一个cover[],一个 14 else XOR[rt] ^= 1;//XOR[] 15 } 16 void PushDown(int rt) 17 { 18 if (cover[rt] != -1) 19 { 20 cover[rt<<1] = cover[rt<<1|1] = cover[rt]; 21 XOR[rt<<1] = XOR[rt<<1|1] = 0; 22 cover[rt] = -1; 23 } 24 if (XOR[rt]) 25 { 26 FXOR(rt<<1); 27 FXOR(rt<<1|1); 28 XOR[rt] = 0; 29 } 30 } 31 void update(char op,int L,int R,int l,int r,int rt) 32 { 33 if (L <= l && r <= R) 34 { 35 if (op == 'U') 36 { 37 cover[rt] = 1; 38 XOR[rt] = 0; 39 } 40 else if (op == 'D') 41 { 42 cover[rt] = 0; 43 XOR[rt] = 0; 44 } 45 else if (op == 'C' || op == 'S') 46 { 47 FXOR(rt); 48 } 49 return; 50 } 51 PushDown(rt); 52 int m = (l + r) >> 1; 53 if (L <= m) update(op , L , R , lson); 54 else if (op == 'I' || op == 'C') 55 { 56 XOR[rt<<1] = cover[rt<<1] = 0; 57 } 58 if (m < R) update(op , L , R , rson); 59 else if (op == 'I' || op == 'C') 60 { 61 XOR[rt<<1|1] = cover[rt<<1|1] = 0; 62 } 63 } 64 void query(int l,int r,int rt) { 65 if (cover[rt] == 1) { 66 for (int it = l ; it <= r ; it ++) 67 { 68 hash[it] = true; 69 } 70 return ; 71 } 72 else if (cover[rt] == 0) 73 return ; 74 PushDown(rt); 75 int m = (l + r) >> 1; 76 query(lson); 77 query(rson); 78 } 79 int main() { 80 cover[1] = XOR[1] = 0; 81 char op , l , r; 82 int a , b; 83 while ( ~scanf("%c %c%d,%d%c\n",&op , &l , &a , &b , &r) ) 84 { 85 a <<= 1 , b <<= 1; 86 if (l == '(') 87 a ++; 88 if (r == ')') 89 b --;//确立区间 90 if (a > b) 91 { 92 if (op == 'C' || op == 'I') 93 { 94 cover[1] = XOR[1] = 0; 95 } 96 } 97 else 98 update(op , a , b , 0 , maxn , 1);//正常的情况 99 } 100 query(0 , maxn , 1);//打回原形 101 bool flag = false; 102 int s = -1 , e; 103 for (int i = 0 ; i <= maxn ; i ++) 104 { 105 if (hash[i]) 106 { 107 if (s == -1) 108 s = i; 109 e = i; 110 } 111 else 112 { 113 if (s != -1) 114 { 115 if (flag) printf(" "); 116 flag = true; 117 printf("%c%d,%d%c",s&1?'(':'[' , s>>1 , (e+1)>>1 , e&1?')':']'); 118 s = -1; 119 } 120 } 121 } 122 if (!flag) printf("empty set"); 123 puts(""); 124 return 0; 125 }
练习:
poj1436 Horizontally Visible Segments
poj2991 Crane
Another LCIS
Bracket Sequence
区间合并
这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并
poj3667 Hotel
题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边
2 a b:将[a,a+b-1]的房间清空
思路:记录区间中最长的空房间
线段树操作:update:区间替换 query:询问满足条件的最左断点
1 #include <cstdio> 2 3 #include <cstring> 4 5 #include <cctype> 6 7 #include <algorithm> 8 9 using namespace std; 10 11 #define lson l , m , rt << 1 12 13 #define rson m + 1 , r , rt << 1 | 1 14 15 16 17 const int maxn = 55555; 18 19 int lsum[maxn<<2] , rsum[maxn<<2] , msum[maxn<<2]; 20 21 int cover[maxn<<2]; 22 23 24 25 void PushDown(int rt,int m) { 26 27 if (cover[rt] != -1) { 28 29 cover[rt<<1] = cover[rt<<1|1] = cover[rt]; 30 31 msum[rt<<1] = lsum[rt<<1] = rsum[rt<<1] = cover[rt] ? 0 : m - (m >> 1); 32 33 msum[rt<<1|1] = lsum[rt<<1|1] = rsum[rt<<1|1] = cover[rt] ? 0 : (m >> 1); 34 35 cover[rt] = -1; 36 37 } 38 39 } 40 41 void PushUp(int rt,int m) { 42 43 lsum[rt] = lsum[rt<<1]; 44 45 rsum[rt] = rsum[rt<<1|1]; 46 47 if (lsum[rt] == m - (m >> 1)) lsum[rt] += lsum[rt<<1|1]; 48 49 if (rsum[rt] == (m >> 1)) rsum[rt] += rsum[rt<<1]; 50 51 msum[rt] = max(lsum[rt<<1|1] + rsum[rt<<1] , max(msum[rt<<1] , msum[rt<<1|1])); 52 53 } 54 55 void build(int l,int r,int rt) { 56 57 msum[rt] = lsum[rt] = rsum[rt] = r - l + 1; 58 59 cover[rt] = -1; 60 61 if (l == r) return ; 62 63 int m = (l + r) >> 1; 64 65 build(lson); 66 67 build(rson); 68 69 } 70 71 void update(int L,int R,int c,int l,int r,int rt) { 72 73 if (L <= l && r <= R) { 74 75 msum[rt] = lsum[rt] = rsum[rt] = c ? 0 : r - l + 1; 76 77 cover[rt] = c; 78 79 return ; 80 81 } 82 83 PushDown(rt , r - l + 1); 84 85 int m = (l + r) >> 1; 86 87 if (L <= m) update(L , R , c , lson); 88 89 if (m < R) update(L , R , c , rson); 90 91 PushUp(rt , r - l + 1); 92 93 } 94 95 int query(int w,int l,int r,int rt) { 96 97 if (l == r) return l; 98 99 PushDown(rt , r - l + 1); 100 101 int m = (l + r) >> 1; 102 103 if (msum[rt<<1] >= w) return query(w , lson); 104 105 else if (rsum[rt<<1] + lsum[rt<<1|1] >= w) return m - rsum[rt<<1] + 1; 106 107 return query(w , rson); 108 109 } 110 111 int main() { 112 113 int n , m; 114 115 scanf("%d%d",&n,&m); 116 117 build(1 , n , 1); 118 119 while (m --) { 120 121 int op , a , b; 122 123 scanf("%d",&op); 124 125 if (op == 1) { 126 127 scanf("%d",&a); 128 129 if (msum[1] < a) puts("0"); 130 131 else { 132 133 int p = query(a , 1 , n , 1); 134 135 printf("%d\n",p); 136 137 update(p , p + a - 1 , 1 , 1 , n , 1); 138 139 } 140 141 } else { 142 143 scanf("%d%d",&a,&b); 144 145 update(a , a + b - 1 , 0 , 1 , n , 1); 146 147 } 148 149 } 150 151 return 0; 152 153 }
练习:
hdu3308 LCIS
hdu3397 Sequence operation
hdu2871 Memory Control
hdu1540 Tunnel Warfare
CF46-D Parking Lot
扫描线
这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去
最典型的就是矩形面积并,周长并等题
hdu1542 Atlantis
题意:矩形面积并
思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt表示该区间下边比上边多几个
线段树操作:update:区间增减 query:直接取根节点的值
1 #include <cstdio> 2 3 #include <cstring> 4 5 #include <cctype> 6 7 #include <algorithm> 8 9 using namespace std; 10 11 #define lson l , m , rt << 1 12 13 #define rson m + 1 , r , rt << 1 | 1 14 15 16 17 const int maxn = 2222; 18 19 int cnt[maxn << 2]; 20 21 double sum[maxn << 2]; 22 23 double X[maxn]; 24 25 struct Seg { 26 27 double h , l , r; 28 29 int s; 30 31 Seg(){} 32 33 Seg(double a,double b,double c,int d) : l(a) , r(b) , h(c) , s(d) {} 34 35 bool operator < (const Seg &cmp) const { 36 37 return h < cmp.h; 38 39 } 40 41 }ss[maxn]; 42 43 void PushUp(int rt,int l,int r) { 44 45 if (cnt[rt]) sum[rt] = X[r+1] - X[l]; 46 47 else if (l == r) sum[rt] = 0; 48 49 else sum[rt] = sum[rt<<1] + sum[rt<<1|1]; 50 51 } 52 53 void update(int L,int R,int c,int l,int r,int rt) { 54 55 if (L <= l && r <= R) { 56 57 cnt[rt] += c; 58 59 PushUp(rt , l , r); 60 61 return ; 62 63 } 64 65 int m = (l + r) >> 1; 66 67 if (L <= m) update(L , R , c , lson); 68 69 if (m < R) update(L , R , c , rson); 70 71 PushUp(rt , l , r); 72 73 } 74 75 int Bin(double key,int n,double X[]) { 76 77 int l = 0 , r = n - 1; 78 79 while (l <= r) { 80 81 int m = (l + r) >> 1; 82 83 if (X[m] == key) return m; 84 85 if (X[m] < key) l = m + 1; 86 87 else r = m - 1; 88 89 } 90 91 return -1; 92 93 } 94 95 int main() { 96 97 int n , cas = 1; 98 99 while (~scanf("%d",&n) && n) { 100 101 int m = 0; 102 103 while (n --) { 104 105 double a , b , c , d; 106 107 scanf("%lf%lf%lf%lf",&a,&b,&c,&d); 108 109 X[m] = a; 110 111 ss[m++] = Seg(a , c , b , 1); 112 113 X[m] = c; 114 115 ss[m++] = Seg(a , c , d , -1); 116 117 } 118 119 sort(X , X + m); 120 121 sort(ss , ss + m); 122 123 int k = 1; 124 125 for (int i = 1 ; i < m ; i ++) { 126 127 if (X[i] != X[i-1]) X[k++] = X[i]; 128 129 } 130 131 memset(cnt , 0 , sizeof(cnt)); 132 133 memset(sum , 0 , sizeof(sum)); 134 135 double ret = 0; 136 137 for (int i = 0 ; i < m - 1 ; i ++) { 138 139 int l = Bin(ss[i].l , k , X); 140 141 int r = Bin(ss[i].r , k , X) - 1; 142 143 if (l <= r) update(l , r , ss[i].s , 0 , k - 1, 1); 144 145 ret += sum[1] * (ss[i+1].h - ss[i].h); 146 147 } 148 149 printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ret); 150 151 } 152 153 return 0; 154 155 }
hdu1828 Picture
题意:矩形周长并
思路:与面积不同的地方是还要记录竖的边有几个(numseg记录),并且当边界重合的时候需要合并(用lbd和rbd表示边界来辅助)
线段树操作:update:区间增减 query:直接取根节点的值
1 #include <cstdio> 2 #include <cstring> 3 #include <cctype> 4 #include <algorithm> 5 using namespace std; 6 #define lson l , m , rt << 1 7 #define rson m + 1 , r , rt << 1 | 1 8 const int maxn = 22222; 9 struct Seg{ 10 int l , r , h , s; 11 Seg() {} 12 Seg(int a,int b,int c,int d):l(a) , r(b) , h(c) , s(d) {} 13 bool operator < (const Seg &cmp) const { 14 return h < cmp.h; 15 } 16 17 }ss[maxn]; 18 bool lbd[maxn<<2] , rbd[maxn<<2]; 19 int numseg[maxn<<2]; 20 int cnt[maxn<<2]; 21 int len[maxn<<2]; 22 void PushUP(int rt,int l,int r) { 23 if (cnt[rt]) { 24 lbd[rt] = rbd[rt] = 1; 25 len[rt] = r - l + 1; 26 numseg[rt] = 2; 27 } else if (l == r) { 28 len[rt] = numseg[rt] = lbd[rt] = rbd[rt] = 0; 29 } else { 30 lbd[rt] = lbd[rt<<1]; 31 rbd[rt] = rbd[rt<<1|1]; 32 len[rt] = len[rt<<1] + len[rt<<1|1]; 33 numseg[rt] = numseg[rt<<1] + numseg[rt<<1|1]; 34 if (lbd[rt<<1|1] && rbd[rt<<1]) numseg[rt] -= 2;//两条线重合 35 } 36 } 37 void update(int L,int R,int c,int l,int r,int rt) { 38 if (L <= l && r <= R) { 39 cnt[rt] += c; 40 PushUP(rt , l , r); 41 return ; 42 } 43 int m = (l + r) >> 1; 44 if (L <= m) update(L , R , c , lson); 45 if (m < R) update(L , R , c , rson); 46 PushUP(rt , l , r); 47 } 48 int main() { 49 int n; 50 while (~scanf("%d",&n)) { 51 int m = 0; 52 int lbd = 10000, rbd = -10000; 53 for (int i = 0 ; i < n ; i ++) { 54 int a , b , c , d; 55 scanf("%d%d%d%d",&a,&b,&c,&d); 56 lbd = min(lbd , a); 57 rbd = max(rbd , c); 58 ss[m++] = Seg(a , c , b , 1); 59 ss[m++] = Seg(a , c , d , -1); 60 } 61 sort(ss , ss + m); 62 int ret = 0 , last = 0; 63 for (int i = 0 ; i < m ; i ++) { 64 if (ss[i].l < ss[i].r) update(ss[i].l , ss[i].r - 1 , ss[i].s , lbd , rbd - 1 , 1); 65 ret += numseg[1] * (ss[i+1].h - ss[i].h); 66 ret += abs(len[1] - last); 67 last = len[1]; 68 } 69 printf("%d\n",ret); 70 } 71 return 0; 72 }
练习
hdu3265 Posters
hdu3642 Get The Treasury
poj2482 Stars in Your Window
poj2464 Brownie Points II
hdu3255 Farming
ural1707 Hypnotoad’s Secret
uva11983 Weird Advertisement
线段树与其他结合练习(欢迎大家补充):
hdu3333 Turing Tree
hdu3874 Necklace
hdu3016 Man Down
hdu3340 Rain in ACStar
zju3511 Cake Robbery
UESTC1558 Charitable Exchange
CF85-D Sum of Medians
spojGSS2 Can you answer these queries II