bzoj 1818: [Cqoi2010]内部白点 (扫描线+线段树)

本文介绍一种算法,用于解决无限大正方形网格中黑色顶点扩散的问题。通过坐标离散化和线段树数据结构,实现高效计算最终黑色顶点的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

传送门

题目大意:无限大正方形网格里有n个黑色的顶点,所有其他顶点都是白色的(网格的顶点即坐标为整数的点,又称整点)。每秒钟,所有内部白点同时变黑,直到不存在内部白点为止。你的任务是统计最后网格中的黑点个数。 内部白点的定义:一个白色的整点P(x,y)是内部白点当且仅当P在水平线的左边和右边各至少有一个黑点(即存在x1 < x < x2使得(x1,y)和(x2,y)都是黑点),且在竖直线的上边和下边各至少有一个黑点(即存在y1 < y < y2使得(x,y1)和(x,y2)都是黑点)。

题解

将坐标离散化,对于每一行维护存在黑点的最靠左/右的列,左端+1的位置权值+1,右端的位置权值-1
然后我们找出同列的相邻两个黑点的区间。
从左到右依次扫描每一列,将所有的在该列以及之前的操作都加入到线段树中,然后查询当前列区间的权值,计入答案即可。

代码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define N 500003
#define LL long long 
using namespace std;
struct data{
    int opt,x,l,r,val;
}q[N];
struct node{
    int x,y;
}a[N];
int n,x[N],y[N],dx[N],dy[N],L[N],R[N],st[N],cnt;
LL tr[N*4];
int cmp(node a,node b)
{
    return a.y<b.y||a.y==b.y&&a.x<b.x;
}
int cmp1(data a,data b)
{
    return a.x<b.x||a.x==b.x&&a.opt<b.opt;
}
void update(int now)
{
    tr[now]=tr[now<<1]+tr[now<<1|1];
}
void pointchange(int now,int l,int r,int x,LL val)
{
    if (l==r) {
        tr[now]+=val;
        return;
    }
    int mid=(l+r)/2;
    if (x<=mid) pointchange(now<<1,l,mid,x,val);
    else pointchange(now<<1|1,mid+1,r,x,val);
    update(now);
}
LL query(int now,int l,int r,int ll,int rr)
{
    if (ll>rr) return 0;
    if (ll<=l&&r<=rr) return tr[now];
    int mid=(l+r)/2;
    LL ans=0;
    if (ll<=mid) ans+=query(now<<1,l,mid,ll,rr);
    if (rr>mid) ans+=query(now<<1|1,mid+1,r,ll,rr);
    return ans;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        scanf("%d%d",&a[i].x,&a[i].y);
        dx[++cnt]=a[i].x; dy[cnt]=a[i].y;
        dx[++cnt]=a[i].x-1; dy[cnt]=a[i].y-1;
        dx[++cnt]=a[i].x+1; dy[cnt]=a[i].y+1;
    }
    sort(dx+1,dx+cnt+1);
    sort(dy+1,dy+cnt+1);
    int cx=unique(dx+1,dx+cnt+1)-dx-1;
    int cy=unique(dy+1,dy+cnt+1)-dy-1;
    for (int i=1;i<=n;i++) 
     a[i].x=lower_bound(dx+1,dx+cx+1,a[i].x)-dx,
     a[i].y=lower_bound(dy+1,dy+cy+1,a[i].y)-dy;
    for (int i=1;i<=cx;i++) L[i]=cy+1;
    for (int i=1;i<=n;i++) L[a[i].x]=min(L[a[i].x],a[i].y),R[a[i].x]=max(R[a[i].x],a[i].y);
    sort(a+1,a+n+1,cmp); cnt=0;
    for (int i=1;i<=cx;i++) 
    if (L[i]!=cy+1&&L[i]!=R[i]){
        ++cnt; q[cnt].opt=1; q[cnt].x=L[i]+1; q[cnt].l=i; q[cnt].val=1;
        ++cnt; q[cnt].opt=1; q[cnt].x=R[i]; q[cnt].l=i; q[cnt].val=-1;
    }
    int l=1;
    for (int i=1;i<=cy;i++) {
        int t1=0; 
        while (a[l].y==i&&l<=n) {
            st[++t1]=a[l].x;
            l++;
        }
        for (int j=2;j<=t1;j++) {
            ++cnt; q[cnt].opt=2; q[cnt].x=i; q[cnt].l=st[j-1]+1; q[cnt].r=st[j]-1;
        }
    }
    sort(q+1,q+cnt+1,cmp1);
    LL ans=n; l=1;
    for (int i=1;i<=cy;i++) {
        while (q[l].x<=i&&l<=cnt) {
            if (q[l].opt==1) pointchange(1,1,cx,q[l].l,q[l].val);
            else ans+=query(1,1,cx,q[l].l,q[l].r);
            l++;
        }
    }
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值