poj3109 Inner Vertices(树状数组)

题目大意:

一个无限大的棋盘上有无数个点,这些点有黑有白,下面进行一步操作,如果有一个点,上下左右各有点,则将这个点涂成黑色。最后计算这个棋盘上黑点的数量。

思路:

这题算是我做得比较满意的一道题,也是在没有借助外界帮助的情况下,比我预期的更短的时间内A了出来(虽然贡献了两次WA)。

第一步,坐标离散化!首先,这题的棋盘的坐标的绝对值能够达到10^9这个数量级,然而点的数量却只有10^5个。这样就很容易想到先使用离散化的方法,将已知的这些坐标离散化。俗称坐标压缩,这样坐标的绝对值就能压缩到10^5这个数量级。

第二步,扫描线算法!如果用朴素的枚举的话,算法复杂度要达到O(N²)的数量级,对于10^5这一数量级,毫无疑问是会超时的。但仔细观察就会发现,如果我们知道确定了同一列中的两点,这两点之间要被涂黑的点的数量就等于这一列的左右各有点的行数之和。因此我们可以借助扫描线算法,先维护一个数组sum,从左向右扫描,碰到点,如果不是这一行的末点,且前面没有碰到点的话,设这一行的坐标为x,那么sum[x]就加一,碰到末点sum[x]就减1。这样,同一列中的两点的横坐标为x1,x2,即可通过计算sum[x1+1]+sum[x1+2]+...sum[x2]可以获得这两点间应增加的黑点数。

第三步,构建树状数组!因为sum中的某一区间会多次求和,sum中的某一点的值也会多次改变,所以如果用朴素的求和,时间复杂度太高,必定会超时。因此我们就借用树状数组,进行动态求和,剩下的步骤就很简单了。在这题中我只建了一个树状数组,不过A完以后我也瞻仰了一下别人的题解,他们有些人使用了两个树状数组去解这道题。不过具体的方法我没有细看,有空的时候就细细地看一下吧。下面上代码!

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n,hash[200000],sum[200000];
struct atp
{
    int x,y;
}data[200000];
int zm[200000];

//树状数组
void add(int i,int x)
{
    while(i<=n)
    {
        sum[i]+=x;
        i+=i&-i;
    }
}

int query(int l,int r)
{
    int right=0,left=0;
    l--;
    while(l>0)
    {
        left+=sum[l];
        l-=l&-l;
    }
    while(r>0)
    {
        right+=sum[r];
        r-=r&-r;
    }
    return(right-left);
}

bool cmpx(atp a,atp b)
{
    if(a.x!=b.x)
        return(a.x<b.x);
    else
        return(a.y<b.y);
}

bool cmpy(atp a,atp b)
{
    if(a.y!=b.y)
        return(a.y<b.y);
    else
        return(a.x<b.x);
}

int main()
{
    scanf("%d",&n);
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&data[i].x,&data[i].y);
    }
    //坐标离散化
    sort(data+1,data+n+1,cmpx);
    int xn=1;
    for(int i=1;i<=n;i++)
    {
        int flag=0;
        if(data[i].x!=data[i+1].x)
            flag=1;
        data[i].x=xn;
        if(flag==1)
            xn++;
    }
    sort(data+1,data+n+1,cmpy);
    int yn=1;
    for(int i=1;i<=n;i++)
    {
        int flag=0;
        if(data[i].y!=data[i+1].y)
            flag=1;
        data[i].y=yn;
        if(flag==1)
            yn++;
    }
    //初始化纵末
    memset(zm,0,sizeof(zm));
    for(int i=1;i<=n;i++)
    {
        if(zm[data[i].x]<data[i].y)
            zm[data[i].x]=data[i].y;
    }
    //扫描线
    int result=0;
    memset(hash,0,sizeof(hash));
    for(int i=1;i<n;i++)
    {
        if(hash[data[i].x]==0&&data[i].y!=zm[data[i].x])
        {
            add(data[i].x,1);
            hash[data[i].x]=1;
        }
        if(data[i].y==data[i+1].y)
        {
            if(data[i].x!=data[i+1].x)
                result+=query(data[i].x+1,data[i+1].x-1);
        }
        //对扫过的进行维护
        if(hash[data[i].x]==1&&data[i].y==zm[data[i].x])
        {
            hash[data[i].x]=0;
            add(data[i].x,-1);
        }
    }
    printf("%d\n",result+n);
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值