zoukankan      html  css  js  c++  java
  • 【bzoj4237】稻草人 分治+单调栈+二分

    题目描述

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

    输入

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

    输出

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

    样例输入

    4
    0 0
    2 2
    3 4
    4 3

    样例输出

    3


    题解

    自己yy出来的分治+单调栈+二分

    如果直接对于每个点求以它为顶点的满足条件的矩形数目比较难求,所以考虑分治处理。

    按x分治,处理完左右区间后处理左边对右边的影响。

    此时左半部分的x严格小于右半部分的x,如果再按x排序则没有意义,所以按照y排序。

    按照y从小到大排序后,遍历区间内所有的点。对于每个右半部分的点,在左半部分里寻找答案。

    我们思考:如果在左半部分扫到了这样两个点a、b:ay>by且ax>bx,那么在按y从小到大遍历时,剩下的点的y都比这两个点大,画图可知b不可能再形成矩形,所以弹掉。

    即对左半部分维护一个从栈底到栈顶x值递减的单调栈,碰到不满足条件的则弹出。

    再考虑右半部分:如果扫到了这样两个点a、b:ay>by且ax<bx,那么这两个点是互不影响的,直接寻找答案即可。所以b没有意义,所以弹掉。而当ay>by且ax>bx时,b限制了a形成的矩形的范围,所以应当保留,并且在遍历到a时在左半部分中二分。

    即对右半部分维护一个从栈底到栈顶x值递增的单调栈,碰到不满足条件的则弹出。

    综上,我们维护两个单调栈,每次遍历到一个点,就把它压到对应的单调栈中,如果这个点是右半部分的点,就在左半部分的单调栈中二分求出比栈顶元素y值大的点的个数,并累加到答案中。注意左右部分的单调栈是不同的,因为它们的意义是不同的。

    这样做的时间复杂度是$O(nlog^2n)$,亲测使用归并排序,二分的常数极小,可以使时间减到5s左右。

    #include <cstdio>
    #include <algorithm>
    #define N 200010
    using namespace std;
    struct data
    {
    	int x , y;
    }a[N] , tmp[N];
    int s1[N] , t1 , s2[N] , t2;
    long long ans;
    bool cmp(data a , data b)
    {
    	return a.x < b.x;
    }
    int getnum(int t)
    {
    	int l = 1 , r = t1 , mid , tmp = t1 + 1;
    	while(l <= r)
    	{
    		mid = (l + r) >> 1;
    		if(a[s1[mid]].y >= t) tmp = mid , r = mid - 1;
    		else l = mid + 1;
    	}
    	return t1 - tmp + 1;
    }
    void solve(int l , int r)
    {
    	if(l >= r) return;
    	int mid = (l + r) >> 1 , tx = a[mid].x , i , p1 = l , p2 = mid + 1;
    	solve(l , mid);
    	solve(mid + 1 , r);
    	for(i = l ; i <= r ; i ++ )
    	{
    		if(p2 > r || (p1 <= mid && a[p1].y < a[p2].y)) tmp[i] = a[p1 ++ ];
    		else tmp[i] = a[p2 ++ ];
    	}
    	t1 = t2 = 0;
    	for(i = l ; i <= r ; i ++ )
    	{
    		a[i] = tmp[i];
    		if(a[i].x <= tx)
    		{
    			while(t1 && a[i].x > a[s1[t1]].x) t1 -- ;
    			s1[++t1] = i;
    		}
    		else
    		{
    			while(t2 && a[i].x < a[s2[t2]].x) t2 -- ;
    			s2[++t2] = i;
    			ans += getnum(a[s2[t2 - 1]].y);
    		}
    	}
    }
    int main()
    {
    	int n , i;
    	scanf("%d" , &n);
    	for(i = 1 ; i <= n ; i ++ ) scanf("%d%d" , &a[i].x , &a[i].y);
    	sort(a + 1 , a + n + 1 , cmp);
    	solve(1 , n);
    	printf("%lld
    " , ans);
    	return 0;
    }
    

     

  • 相关阅读:
    关于闹钟的题
    【历史】- UNIX发展史(BSD,GNU,linux)
    使用EF操作Mysql数据库中文变问号的解决方案
    javascript方法的方法名慎用close
    使用VS2013 + EF6 + .NET4.5 连接Mysql数据库
    ADO.NET生成的数据库连接字符串解析
    在WebBrowser控件使用js调用C#方法
    Mysql数据库之auto_increment
    Visual Studio插件Resharper 2016.1 及以上版本激活方法【亲测有效】
    Windows下Mysql5.7开启binlog步骤及注意事项
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7123308.html
Copyright © 2011-2022 走看看