标签:
传送门:http://codeforces.com/problemset/problem/612/D
(转载请注明出处谢谢)
题意:
给出数字n和k,n表示接下来将输入n个在x轴上的闭区间[li,ri],找出被包含了至少k次的点,并求由这些点组成的连续区间的数目,并使该数目最小。输出该数目并将区间从左到右(x的正方向)输出。
比如样例1,给出区间[0,5],[-3,2],[3,8],那么被覆盖了至少两次的区间就是[0,2],[3,5],有两个。
解题思路:
处理区间覆盖,一上来就想到了前缀和,普通前缀和处理的话,以数值为下标,然后开一个标记数组,每次对于区间[l,r],tmp[l]++,tmp[r+1]--,这样求前缀和数组s后,s[i]即为i被覆盖的次数,因此只需要如此操作一遍后,O(n)扫一遍,连续的s[x]>=k的,就为一个区间,记录输出即可。
随后注意到,l和r的范围在[-1e9,1e9],开不了数组,但n只为1e6,每次输入最多2e6个数字,因此采用离散化处理。将输入是数字排序后,扫一遍所有区间,每次用lower_bound来求它所在的位置,因为一定存在,所以得到的下标一定是该数字的第一个下标(如果有重复,则为第一个该数字的下标)。
在运行样例时,发现出来的结果不对。留意到是因为此时运行出来的前缀和数组s[]和想要的不一样。一般情况下,前缀和处理区间覆盖问题时“tmp[l]++,tmp[r+1]--”中的l和r指的是数值,而离散化后指的是它的下标,在处理的时候就会发生冲突。
为了解决这个冲突,只要给每个数字都多“复制”一次就可以了,因为此时tmp[r+1]中的r+1所指是一个“多余”的位置,就不会出现冲突的情况。(语言表述有点无力,下面以第一个样例为例)
区间[0,5],[-3,2],[3,8],输入后为a[]={-3,0,2,3,5,8},然后按上述所说求前缀和数组为s[]={1,2,2,2,2,1}。复制后,a[]={-3,-3,0,0,2,2,3,3,5,5,8,8},前缀和数组为s[]={1,1,2,2,2,1,2,2,2,1,1},此时s数组中值为2的连续区间上的第一个数和第二个数(相同下标的a数组)恰好是具体区间,冲突解决。
具体实现细节看代码。
代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=1e6+10; int n,k,cnt,a[N<<2]; int tmp[N<<2],s[N<<2]; struct seg{ int l,r; }sgt[N],res[N]; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&k)){ cnt=0; for(int i=0;i<n;i++){ scanf("%d%d",&sgt[i].l,&sgt[i].r); a[cnt++]=sgt[i].l,a[cnt++]=sgt[i].l; a[cnt++]=sgt[i].r,a[cnt++]=sgt[i].r; } sort(a,a+cnt); memset(tmp,0,sizeof(tmp)); for(int i=0;i<n;i++){ tmp[lower_bound(a,a+cnt,sgt[i].l)-a]++; tmp[lower_bound(a,a+cnt,sgt[i].r)-a+1]--; } s[0]=tmp[0]; for(int i=1;i<cnt;i++) s[i]=s[i-1]+tmp[i]; int tot=0,flag=0; for(int i=0;i<cnt;i++){ if(s[i]>=k){ if(!flag){ res[tot].l=a[i]; flag=1; } } else{ if(flag){ flag=0; res[tot++].r=a[i-1]; } } } printf("%d\n",tot); for(int i=0;i<tot;i++) printf("%d %d\n",res[i].l,res[i].r); } return 0; }
标签:
原文地址:http://www.cnblogs.com/names-yc/p/5093248.html