[Luogu 3415] 祭坛 (扫描线+BIT)

题目描述:

题目描述太长辣,而且难懂。
其实一个祭坛点只需要找它左边上边下边右边各一个点就可以形成一个需要的保护网。
那么一个祭坛点最多的保护层数即为MIN(上面水晶柱个数,下面水晶柱个数,左边水晶柱个数,右边水晶柱个数).

题目分析:

首先我们可以开一个Vector
记录每个行上有多少的水晶柱.
二分一个答案
用扫描线从上到下扫描。
维护两个数组
UP&DOWN
分别表示i这个点上方有多少的水晶柱,下方有多少水晶柱
显然更新UP&DOWN的值是很容易的,只需要利用我们记录的Vector即可
在上面一行的水晶柱,对UP有+1的贡献
在本行的水晶柱,对DOWN有-1的贡献
对于左右来说,在本行起码得是第mid个柱子后的点和最后一个柱子-mid前的点才可以
一个个扫去查UP&DOWN值是否符合要求?
Naive!
开一个树状数组.
对于UP&DOWN符合要求的前缀和+1,不符合的-1
我们可以通过前缀和来快速知道l-r内符合要求的点的个数。
树状数组都能打错~真没救了。

题目链接:

Luogu 3415

AC 代码:

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <climits>
#define il inline
#define lowbit(x) x&-x
#define mem(a,b,c) memset(a,b,sizeof(c))
using namespace std;
const int maxm=1e5+100;
std::vector <int> vx[maxm];
int siz[maxm],ans1,ans2;
int up[maxm],down[maxm],bl[maxm],br[maxm],n;
int sum[maxm];
bool ok[maxm],a[maxm],b[maxm];
int t1,t2;
il void ins(int x,int nx){for(int i=x;i<=n;i+=lowbit(i)) sum[i]+=nx;}
il int ask(int x){int ans=0;for(int i=x;i;i-=lowbit(i)) ans+=sum[i];return ans;}
il int read()
{
    int x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*w;
}
il void adx(int x,int y)
{
    if(y==ok[x]) return;
    ins(x,y==1?1:-1);
    ok[x]=y;
}
il bool check(int mid)
{
    if(mid==0) 
    {
        ans1=0,ans2=INT_MAX;
        return 1;
    }
    int num=0;

    for(int i=1;i<=n;i++)
    {
        if(vx[i].size()>=mid*2) 
        {
          bl[i]=vx[i][mid-1]+1;
          br[i]=vx[i][vx[i].size()-mid]-1;
        }
        else bl[i]=n+1,br[i]=0;
        //printf("-%d %d-\n",bl[i],br[i]);
        up[i]=0,down[i]=siz[i];
        sum[i]=0,a[i]=0,b[i]=0,ok[i]=0;
    }
    for(int i=1;i<=n;i++)
    {
        std::vector <int> res;
        for(int j=0;j<vx[i-1].size();j++)
        {
            up[vx[i-1][j]]++;
            a[vx[i-1][j]]=(up[vx[i-1][j]]>=mid);
            res.push_back(vx[i-1][j]);
        }
        for(int j=0;j<vx[i].size();j++)
        {
            down[vx[i][j]]--;
            b[vx[i][j]]=(down[vx[i][j]]>=mid);
            res.push_back(vx[i][j]);
        }
        for(int j=0;j<res.size();j++)
         adx(res[j],(a[res[j]]+b[res[j]])==2);
        num+=bl[i]<=br[i]?ask(br[i])-ask(bl[i]-1):0;
    }
    if(!num) return 0;
    ans1=mid,ans2=num;
    return 1;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int xx=read(),yy=read();
        xx++,yy++;
        vx[xx].push_back(yy);
        siz[yy]++;
    }
    int l=0,r=n;
    n++;
    for(int i=1;i<=n;i++) std::sort(vx[i].begin(),vx[i].end());
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid)) l=mid+1;
        else r=mid-1; 
    }
    printf("%d\n%d",ans1,ans2);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值