JZOJ 3809. 【NOIP2014模拟8.25】设备塔

Description

为了封印辉之环,古代塞姆利亚大陆的人民在异空间中建造了一座设备塔。
简单的说,这座设备塔是一个漂浮在异空间中的圆柱体,圆柱体两头的圆是计算核心,而侧面则是
传输信息所用的数据通道,划分成 Nm 个区块。
然而,随着工作的继续进行,他们希望把侧面的一部分区块也改造成其他模块。然而,任何时候都
必须保证存在一条数据通道,能从圆柱体的一端通向另一端。
由于无法使用辉之环掌控下的计算系统,他们寻求你的帮助来解决这个问题。他们将逐个输入想要
改造的区域,而你则执行所有可行的改造并忽略可能导致数据中断的改造。

Input

第一行,包含两个整数 N;M;K ,表示侧面的长和宽,以及操作数。
接下来K 行,每行包含三个整数 xi;yi ,表示操作的区块的坐标。
数据保证不会对已经操作成功的区块进行操作。

Output

输出一行,表示有多少个操作可以被执行。

Sample Input

3 4 9
2 2
3 2
2 3
3 4
3 1
1 3
2 1
1 1
1 4

Sample Output

6

Data Constraint

• 对于分值为30 的子任务1,保证 N;M<=100;K<=5000
• 对于分值为30 的子任务2,保证 N;M<=3000;K<=5000
• 对于分值为40 的子任务3,保证 N;M<=3000;K<=300000

Hint

Hint

Solution

  • 这道题是一道神奇的模拟题!!!

  • 很容易想到的普通暴力枚举在这个超大范围下也无能为力~

  • 这里有一个利用并查集的巧妙方法

  • 首先,注意到路径可以横跨左右边界

  • 所以,把这个图向右复制一份,即 长*2 ,点都双份处理

  • 每一个点(编号排成一列地处理)与八连通的点进行并查集

  • 那么如何判断加点时,所构成的联通快有没有横向封盖呢?

  • 于是设一个标记数组,记号可以设为数据编号这种独一无二的号码

  • 将要加入的点在左边地图里的八连通点,这些点的父亲编号在标记数组里赋值

  • 然后在右边地图里的八连通点的父亲编号在标记数组里查询

  • 如果已赋值,说明加入的话会构成横向封盖,则不能加入!

  • 这样,我们就能方便的判断,省去了在并查集数组里加加删删的麻烦!

  • 这样巧妙地运用并查集,使时间复杂度降到了 O(K) 再加一些常数!!!

Code

#include<cstdio>
using namespace std;
const int N=3001,M=2*N*N;
const int way[8][2]={{0,1},{1,0},{0,-1},{-1,0},
                     {1,1},{-1,1},{-1,-1},{1,-1}};
int n,m,cnt,ans;
int f[M],g[M];
bool bz[M];
bool pd;
inline int read()
{
    int data=0; char ch=0;
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();
    return data;
}
inline int get(int x,int y){return (x-1)*2*m+y;}
inline int find(int x){return (f[x]==x)?x:f[x]=find(f[x]);}
inline void work(int x,int y)
{
    int p=get(x,y);
    bz[p]=true;
    for(int i=0;i<8;i++)
    {
        int xx=x+way[i][0],yy=y+way[i][1];
        if(!xx || xx>n) continue;
        if(!yy) yy+=2*m; else if(yy>2*m) yy-=2*m;
        int q=get(xx,yy);
        if(bz[q])
        {
            int f1=find(p),f2=find(q);
            if(f1!=f2) f[f1]=f2;
        }       
    }
}
int main()
{
    n=read(),m=read(),cnt=get(n,2*m);
    for(int i=1;i<=cnt;i++) f[i]=i;
    int k=read()+1;
    while(--k)
    {
        int x=read(),y=read();
        for(int i=0;i<8;i++)
        {
            int xx=x+way[i][0],yy=y+way[i][1];
            if(!xx || xx>n) continue;
            if(!yy) yy+=2*m; else if(yy>2*m) yy-=2*m;
            int p=get(xx,yy);
            if(bz[p]) g[find(p)]=k;
        }
        for(int i=pd=0;i<8;i++)
        {
            int xx=x+way[i][0],yy=y+m+way[i][1];
            if(!xx || xx>n) continue;
            if(!yy) yy+=2*m; else if(yy>2*m) yy-=2*m;
            int p=get(xx,yy);
            if(bz[p] && g[find(p)]==k){pd=true;break;}
        }
        if(!pd)
        {
            ans++;
            work(x,y);
            work(x,y+m);
        }
    }
    printf("%d",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值