【学校OJ】 线段树 影子的宽度&盒子的个数

来源:互联网 发布:程序员接私活app 编辑:程序博客网 时间:2024/04/29 16:15

【影子的宽度】

题目描述

桌子上零散地放着若干个盒子,盒子都平行于墙。桌子的后方是一堵墙。如图所示。现在从桌子的前方射来一束平行光, 把盒子的影子投射到了墙上。问影子的总宽度是多少?

输入

第1行:3个整数L,R,N。-100000 <=L<=R<= 100000,表示墙所在的区间;1<=N<=100000,表示盒子的个数
接下来N行,每行2个整数BL, BR,-100000 <=BL<=BR<= 100000,表示一个盒子的左、右端点(左闭右开)

输出

第1行:1个整数W,表示影子的总宽度。

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

Sample Input 10 7 21 24 5Sample Input 2-10 10 2-5 2-2 2Sample Input 3-10 10 3-7 0-4 9-4 2Sample Input 4-100 100 3-7 25 92 5Sample Input 5-50 50 4-2 40 69 10-5 30

样例输出

Sample Output 12Sample Output 27Sample Output 316Sample Output 416Sample Output 5

35

【盒子的个数】

题目描述

桌子上零散地放着若干个盒子,盒子都平行于墙。桌子的后方是一堵墙。如图所示。问从桌子前方可以看到多少个盒子?假设人站得足够远。

输入

第1行:3个整数L,R,N。-100000 <=L<=R<= 100000,表示墙所在的区间;1<=N<=100000,表示盒子的个数
接下来N行,每行2个整数BL, BR,-100000 <=BL<=BR<= 100000,表示一个盒子的左、右端点(左闭右开)。越在前面输入的盒子越排在离墙近的位置,后输入的盒子排在离墙远的位置。

输出

第1行:1个整数M,表示可看到的盒子个数。

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

1 10 52 63 64 61 23 6

样例输出

3


    这两道题很久以前就AC了,现在来当作模板题总结一下线段树的写法。

    对于这两道孪生题目,也是不能说什么了……连图都节约资源用同一张……那么就总结一下吧!

    对于第一题,相较于第二题还是很简单的,先建造出[L,R]的线段树(建树就不慢慢说了),然后用一个成员p记录区间状态:1=>全盖、0=>全裸、-1=>半盖半裸(有点像四象树)。

    每次如果完全覆盖一个区间,不管怎样都将这个区间标记为全盖,然后返回。而如果无法完全覆盖,就分成左子区间和右子区间,递归操作。

    但请注意,因为每次覆盖时都使用了‘懒操作’(lazy),其实我们只改变了最大一个区间的状态值,每次递归时需要将新的状态传递给儿子,并改变儿子,才能避免出错。同时,进行最后的查询用递归固然方便,但每次都有可能花费O(n)的时间,对于多次操作,我们可以新增一个成员sum,记录区间内的影子宽度,方便查询,不过要记得和p一起维护哦。

#include<cstdio>#include<algorithm>using namespace std;const int M=100000;struct node{int p,l,r;}A[8*M+5];int K[M+5][2];int ml,mr,n;void make(int i,int l,int r){A[i].l=l;A[i].r=r;if(l==r)return;make(i*2,l,((l+r)>>1));make(i*2+1,((l+r)>>1)+1,r);}void insert(int q,int l,int r){if(l<=A[q].l&&r>=A[q].r){A[q].p=A[q].r-A[q].l+1;return;}if(A[q].l>r||A[q].r<l)return;if(A[q].p==A[q].r-A[q].l+1)return;if(A[q].l<A[q].r){insert(2*q,l,r);insert(2*q+1,l,r);}A[q].p=A[2*q].p+A[2*q+1].p;}int sum(){return A[1].p;}int main(){scanf("%d%d%d",&ml,&mr,&n);mr--;for(int i=1;i<=n;i++){scanf("%d%d",&K[i][0],&K[i][1]);K[i][1]--;}make(1,ml,mr);for(int i=1;i<=n;i++)insert(1,K[i][0],K[i][1]);printf("%d",sum());}
    而对于第二题,我们必须换一种思路。我们这次的小p就不能只承担0、1、-1的任务了,它现在有很多种状态:a(a>0)表示区间的盒子全为a这一个、0表示区间没有任何盒子、-1表示区间内盒子混杂,不止一个。然后每次用同样的方法去将一个区间进行覆盖(同样需要下传p)。然后最后的时候,实在没办法使用可爱的sum(因为盒子被遮挡,可能在区间内不连续,甚至被隔成几段),我们进行一次O(n)的递归,将它们标记进数组就可以用一次循环进行统计了。

#include<cstdio>#include<algorithm>using namespace std;const int M=100000;struct node{int color,l,r;}A[8*M+5];int K[M+5][2];int vis[M+5];int ml,mr,n,ans;void make(int i,int l,int r){A[i].l=l;A[i].r=r;if(l==r)return;make(i*2,l,((l+r)>>1));make(i*2+1,((l+r)>>1)+1,r);}void insert(int q,int l,int r,int c){if(l<=A[q].l&&r>=A[q].r){A[q].color=c;return;}if(A[q].l>r||A[q].r<l)return;if(A[q].color>=0){A[2*q].color=A[2*q+1].color=A[q].color;A[q].color=-1;}if(A[q].l<A[q].r){insert(2*q,l,r,c);insert(2*q+1,l,r,c);}}void sum(int q){if(A[q].color>0){if(!vis[A[q].color]){ans++;vis[A[q].color]=1;}}else{if(A[q].color==-1){sum(2*q);sum(2*q+1);}}}int main(){scanf("%d%d%d",&ml,&mr,&n);mr--;for(int i=1;i<=n;i++){scanf("%d%d",&K[i][0],&K[i][1]);K[i][1]--;}make(1,ml,mr);for(int i=1;i<=n;i++)insert(1,K[i][0],K[i][1],i);sum(1);printf("%d",ans);}


0 0