[BZOJ]4237 稻草人 CDQ分治 详细题解

来源:互联网 发布:淘宝买精密管警察找我 编辑:程序博客网 时间:2024/06/06 03:09

4237: 稻草人

Time Limit: 40 Sec  Memory Limit: 256 MB
Submit: 855  Solved: 374
[Submit][Status][Discuss]

Description

JOI村有一片荒地,上面竖着N个稻草人,村民们每年多次在稻草人们的周围举行祭典。
有一次,JOI村的村长听到了稻草人们的启示,计划在荒地中开垦一片田地。和启示中的一样,田地需要满足以下条件:
田地的形状是边平行于坐标轴的长方形;
左下角和右上角各有一个稻草人;
田地的内部(不包括边界)没有稻草人。
给出每个稻草人的坐标,请你求出有多少遵从启示的田地的个数

Input

第一行一个正整数N,代表稻草人的个数
接下来N行,第i行(1<=i<=N)包含2个由空格分隔的整数Xi和Yi,表示第i个稻草人的坐标

Output

输出一行一个正整数,代表遵从启示的田地的个数

Sample Input

4
0 0
2 2
3 4
4 3

Sample Output

3

HINT

所有满足要求的田地由下图所示:

 

1<=N<=2*10^5

0<=Xi<=10^9(1<=i<=N)

0<=Yi<=10^9(1<=i<=N)

Xi(1<=i<=N)互不相同。

Yi(1<=i<=N)互不相同。


Source

JOI 2013~2014 春季training合宿 竞技3 By PoPoQQQ

                                                                                            [Submit][Status][Discuss]

题解

         这道题可以用cdq分治做,也可以用线段树做...不过cdq分治代码短并且两者时间复杂度相同...所以还是讲讲cdq分治吧.
       我们一开始把整个数组以y值从小到大sort一遍.
       我们开始二分.
       二分这个数列的位置.
       我们会不断的二分,对于某次二分的lf,rg,我们都是先继续不断递归二分这段区间的得到子区间的答案(d等下再说怎么计算区间答案)再来计算这段区间的答案,等下你就会明白为什么.
       对于当前的区间(lf,rg),我们二分了一个mid递归处理完子区间后开始计算这个区间的答案.我们把左区间按x从小到大sort一遍,右区间按x从小到大sort了一遍,此时,我仍能称右区间所有的y值是大于左区间所有的y值的.

    关于sort与结论的正确性    

       为什么呢?我们明明先递归处理左右子区间,虽然一开始把整个数组按y sort了一遍,但是子区间在处理他们自己答案的时候被sort过了,为什么还能保证?
        其实,因为一开始没递归处理左右子区间的时候,这个结论(第6段最后一句)是成立的吧(因为一开始我们按y值sort了一遍),那么你左右子区间各自内部无论经过多少次sort,左区间右区间还是原来的那些元素,所以递归回来以后右区间所有的y值还是大于左区间所有的y值的.

     然后呢

        sort完了之后才重头戏,我们只统计右区间的元素与左区间的元素能形成田地的数目(因为左右区间内部存在的已经被递归计算过了).怎么样才能形成题目中要求的田地呢?首先第一个条件,所选的左区间元素j要比所选的右区间元素i x值大,且y值大.这个很好搞定.我们枚举右区间(从mid+1开始到rg)的每个元素i,左区间枚举j,那么由于x是排过序的,右区间的y又保证了大于左区间的y,对于每个i线性枚举到j.x>i.x(只有y保证了右区间全体大于左区间),那之前的就都是符合条件的.对于下一个i,又于i+1.x>i.x,所以左区间对于i符合条件的对于i+1也满足,那么我们只需要接着上次的j继续线性扫一遍就可以了,时间是o(rg-lf+1)的.
        第一个条件满足了,怎么满足i,j之间所形成的田地没有稻草人(k)?我们对于右区间维护一个y值单调递增的栈.为什么要这么干?因为我们在栈压入当前i的时候,把栈中y值大于他的pop,那pop完毕后的栈顶元素p一定是x与i最近的y小于i的点.那这个点就是最有可能成为k(使i和j构不成田地)的点.因为这个点是在右区间x,y都小于
i且最接近的点.我们就靠这个点的x值(y值右区间全部大于左区间,没法筛)去左区间筛选那些满足第一个条件的点,筛后还能存活下来的点就是满足x值大于k点y值的(这样k就无法在田地中间了).

     就可以了

        那维护上方即右区间的单调栈就可以了吗?
        不要忽视下方即左区间.若筛出来的某点p,若在左区间存在某点q.x>p.x并且q.y>b.y并且p.x<i.x(y值右区间大于左区间,这里不用比较),那也构不成田地,也就说光考虑在右区间对i构成威胁的点是不够的,还要考虑在左区间对j构成威胁的点.
        我们可以如法炮制.我们对左区间维护一个y值单调递减的单调栈,我们二分就在这个左区间里的栈里二分.为什么要这么维护?单调栈里x值本身单调(排了序的哇),这样你二分的时候二分到某p,那栈里比pos大的都可以选,因为他们y值递减,互相之间不会形成威胁,且又是二分筛选过后的,绝对满足.那么怎么保证他们就是所有的点,单调栈pop出去的点会不会是可选的?\设当某点压进栈里的时候比栈顶元素y值大,那么被pop出去的所有元素的x值都比这元素小(sort的结果)且y值小(就是因为小才pop啊),那么这个压进去的点绝对会对那些pop出去的点无法与i点构成田地(再读一下黑体字).

     闲话

        其实是蛮简单的一道题还是写了这么多,如果你不懂的话多读几遍画画图看看代码一定能懂得.
        cdqz人,怎能不会cdq呢.......
#include<stdio.h>#include<algorithm>using namespace std;const int maxn=200010;inline const int read(){   register int f=1,x=0;   register char ch=getchar();   while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}   while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}   return f*x;}struct point{   int x,y;}aa[maxn];long long ans;int s1[maxn],s2[maxn],n;inline bool cmpx(point a,point b){return a.x<b.x;}inline bool cmpy(point a,point b){return a.y<b.y;}void cdqz(int lf,int rg){   if(lf==rg) return;   int mid=(lf+rg)>>1,j=lf,top1=0,top2=0;   cdqz(lf,mid),cdqz(mid+1,rg);   sort(aa+lf,aa+mid+1,cmpx),sort(aa+mid+1,aa+rg+1,cmpx);   for(int i=mid+1;i<=rg;i++){       while(top1&&aa[s1[top1]].y>=aa[i].y) top1--;   s1[++top1]=i;   while(aa[j].x<aa[i].x&&j<=mid){      while(top2&&aa[s2[top2]].y<=aa[j].y) top2--;      s2[++top2]=j;      j++;   }   int l=1,r=top2,std=aa[s1[top1-1]].x,pos=-1;   while(l<=r){       int mid=(l+r)>>1;       if(aa[s2[mid]].x>std) pos=mid,r=mid-1;       else l=mid+1;   }   if(pos!=-1) ans+=top2-pos+1;    }}int main(){   n=read();   aa[0].x=aa[0].y=-1;   for(register int i=1;i<=n;i++) aa[i].x=read(),aa[i].y=read();   sort(aa+1,aa+n+1,cmpy);   cdqz(1,n);   printf("%lld\n",ans);}


         
原创粉丝点击