Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 34272 | Accepted: 9932 |
Description
Input
Output
Sample Input
1 5 1 4 2 6 8 10 3 4 7 10
Sample Output
4
Source
首先提醒下各位,PKU这道题目的数据有所加强,请把你的数组开大,不然会一直WA
题目大意:
往墙上贴海报,可以覆盖,问最后可以看见几张海报。(看见的海报不一定的连在一起的一整张,同一张海报可以分开,但是只能算做一张)
解题思路:
直接奔着线段树的知识点去的,可以看出这是区间替换问题,没有求和操作,所有以前模板中的很多函数可以不用。
题目的意思就是说,所有的海报更新完毕之后,检查整个1---N的区间,看看有几张海报。我们可以设置一个数组。假设有4张海报,那么四张海报的标识依次是1 2 3 4,之后通过更新覆盖操作,最后的时候我们要看一下整个区间还有几个不同的标识,那么就能看见几张海报,而且对于每次遇到的海报,我们都要做相应的标记,以免重复计数。
这道题目的数据范围很大,海报的宽度远远超过了N的范围,所以在做线段树的操作的时候,可以会出现,更新区间的范围超过了线段树数组的范围,如果是直接搞,那么TLE+MLE是肯定的,所以要采用离散化。
我的理解呢,就是把所有的数据排序,然后插入线段树数组的时候按照原来输入数据的顺序插入,但是插入的是序号。这样就会把那些落在数据范围后面的很大的数字抓取到前面的区间来,因为可能从第一个到最后一个元素之间的你都没用,那么你还开那么大的数组 不是浪费的空间?通过标号的方式可以把数据有效的集中起来
下面是HH大牛对于离散化的讲解:
离散化简单的来说就是只取我们需要的值来用,比如说区间[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,4][1,2][3,4]
线段2覆盖了[1,2],线段3覆盖了[3,4],
那么线段1是否被完全覆盖掉了呢?
例子一是完全被覆盖掉了,而例子二没有被覆盖
为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.
线段树功能:update:成段替换 query:简单VIS标记数组
代码如下:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define manx 21111 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 int tot[manx<<4]; int left[manx],right[manx]; int temp[manx*3]; bool vis[manx]; int count1; int bi_search(int x,int n,int *s) { int low,high; low=0,high=n-1; while(low<=high) { int mid=(low+high)>>1; if(x==s[mid]) return mid; if(x<s[mid]) high=mid-1; else if(x>s[mid]) low=mid+1; } return 0; } void pushdown(int rt) { if(tot[rt]!=-1) { tot[rt<<1]=tot[rt<<1|1]=tot[rt]; tot[rt]=-1; } } void update(int L,int R,int add,int l,int r,int rt) { if(L<=l&&r<=R) { tot[rt]=add; //区间替换操作,这个区间内的海报内容被覆盖// return ; } pushdown(rt); int m=(l+r)>>1; if(L<=m)update(L,R,add,lson); if(R>m)update(L,R,add,rson); } void query(int l,int r,int rt) { if(tot[rt]!=-1) { if(vis[tot[rt]]==false) //如果没有标记过,说明是个新海报,要计数// count1++; vis[tot[rt]]=1; return ; } if(l==r) return ; int m=(l+r)>>1; query(lson); query(rson); } int main() { //freopen("f:\\in.txt","r",stdin); int n; int i; int t; int step; scanf("%d",&t); while(t--) { scanf("%d",&n); step=0; for(i=0;i<n;i++) { scanf("%d%d",&left[i],&right[i]); temp[step++]=left[i]; temp[step++]=right[i]; } sort(temp,temp+step); int m=1; for(i=1;i<step;i++) //把输入中重复的起点和重点 去掉// if(temp[i]!=temp[i-1]) temp[m++]=temp[i]; for(i=m-1;i>0;i--) if(temp[i]!=temp[i-1]+1) //离散化处理// temp[m++]=temp[i-1]+1; sort(temp,temp+m); memset(tot,-1,sizeof(tot)); for(i=0;i<n;i++) { int l=bi_search(left[i],m,temp); //插入的时候插入的是排序后的编号,但是对应了实际的元素,可以说标号代表了元素// int r=bi_search(right[i],m,temp); update(l,r,i,0,m-1,1); } memset(vis,0,sizeof(vis)); count1=0; query(0,m-1,1); printf("%d\n",count1); } return 0; }