SMOJ 2439 划分 & [CERC2017] Buffalo Barricades(set+扫描线+并查集+乱搞)

8 篇文章 0 订阅
4 篇文章 0 订阅

题目大意

网格上有n个点(x,y),有q个询问,每次在(a,b)的右上角放一个向左和向下的栅栏,问栅栏围进了多少个点,每个栅栏碰到原来的栅栏或坐标轴就会结束。

这里写图片描述

1<=n,q<=100000
1<=a[i],b[i]<=10^9
1<=x[i],y[i]<=10^9


思路

考试时的我是很naive的,看错题了,以为一个点只能贡献一次答案,写了一个假的CDQ+线段树,样例不过。其实从样例就可以看出一个点可能多次贡献答案。

考虑扫描线,将点和栅栏从上往下、从左向右扫描,对坐标稍微处理一下,让询问比点先发生。

考虑这样一个关系,一个询问在另一个询问的右上方,如果右上方的时间靠前,则答案将贡献过去。我们向第一个这样的询问连边。这个用并查集维护。至于时间我们先不管。如何找到x在当前点右边的第一个呢?平衡树即可。为了方便使用set

将每个栅栏插入平衡树,然后考虑栅栏间的影响,上面处理好了第一种情况。考虑另一种情况:

①A在B的左上方,而A比B先出现,那么A就对B有影响,能贡献到A的答案与B无关;

②A在B的左上方但是出现时间比B晚,那么A不会对B产生任何影响(这取决于点向栅栏贡献的方式),我们要在set中将所有这样的栅栏删掉,因为A已经贡献完了但是在B的左边影响B的答案。

后面就是点对栅栏的贡献方式了,经过上面的处理我们只需要向在点右上方的第一个栅栏贡献就好了。

最后我们要合并答案,就是当A在B的左下方第一个时,只有A有答案而B可能要加上A的。我们按时间倒序拿出所有栅栏,并将其答案加入并查集中,如果A在B的后面出现,就会将答案放进去了,而A的答案已经提前算好了。乱搞一通所有正确的答案就出来了。

时间复杂度O(nlogn)。

细节比较多,具体看代码吧。这真是一道神奇的题目啊。


代码

#include <bits/stdc++.h>
#define maxn 200010

using namespace std;

int n, q;
struct Data{
    int x, y, t;
    bool operator < (const Data& OTHER) const{
        if(y == OTHER.y)  return x < OTHER.x;
        return y > OTHER.y;
    }
}p[maxn];

int par[maxn], cnt[maxn], Fa[maxn], ans[maxn];

int FindR(int x){
    return (!Fa[x]) ? x : Fa[x] = FindR(Fa[x]);
}

void Merge(int x, int y){
    x = FindR(x);
    y = FindR(y);

    if(x != y){  
        Fa[x] = y;
        cnt[y] += cnt[x];
    }
}

set <pair<int, int> > S;

int main(){

    freopen("2439.in", "r", stdin);
    freopen("2439.out", "w", stdout);

    scanf("%d", &n);

    for(int i = 1; i <= n; i++){
        scanf("%d%d", &p[i].x, &p[i].y);
        p[i].x = (p[i].x << 1) - 1;
        p[i].y = (p[i].y << 1) - 1;
        p[i].t = 0;
    }

    scanf("%d", &q);

    for(int i = 1; i <= q; i++){
        scanf("%d%d", &p[n+i].x, &p[n+i].y);
        p[n+i].x <<= 1;
        p[n+i].y <<= 1;
        p[n+i].t = i;
    }

    sort(p+1, p+n+q+1);

    for(int i = 1; i <= n+q; i++){
        pair <int, int> tmp = make_pair(p[i].x, p[i].t);
        if(p[i].t){
            S.insert(tmp);
            set <pair<int, int> >::iterator now = S.find(tmp);
            if((++now) != S.end())  par[p[i].t] = now->second;
            for(;;){
                now = S.find(tmp);
                if(now == S.begin())  break;
                if((--now)->second < p[i].t)  break;
                S.erase(now);
            }
        }
        else{
            set <pair<int, int> >::iterator it = S.lower_bound(tmp);
            if(it != S.end())  ++ cnt[it->second];
        }
    }

    for(int i = q; i; i--){
        ans[i] = cnt[FindR(i)];
        if(par[i])  Merge(i, par[i]);
    }

    for(int i = 1; i <= q; i++)  printf("%d\n", ans[i]);

    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值