中国石油大学ACM俱乐部开放训练赛 问题 G: 奎奎画画 并查集 + 逆向思考

“为你写诗,为你静止,为你做不可能的事”,爱情是一种怪事,它让奎奎开始学习画画。奎奎认为一张画的艺术价值等于画上的白色联通块个数(当一个格子和它上下左右四个方向上的某个相邻格子颜色相同,则认为它们属于同一个联通块),奎奎还认为他作画的艺术价值和妹子对他的好感度紧密相关,因此奎奎非常在意每一时刻他的画的艺术价值。 为了简化题目,奎奎在一张n行m列的白色矩形格子画布上作画,他一共画了q笔,每一笔都是从(x1,y1)格子开始到(x2,y2)格子结束(x1=x2或y1=y2),将所有满足x1<=x<=x2并且y1<=y<=y2的格子涂黑。奎奎想知道当他画完每一笔之后,这幅画的艺术价值是多少。

输入

第一行三个整数n,m,q (1≤n, m≤1000, 1≤q≤10000 )
下面q行每行4个整数x1,y1,x2,y2 (1≤x1≤x2≤n, 1≤y1≤y2≤m)描述奎奎画的q条线段的起点和终点

输出

q行,对于奎奎画的每一条线段,输出一行一个整数表示该线段画完之后画布上白色联通块的个数。

样例输入 Copy

4 6 5
2 2 2 6
1 3 4 3
2 5 3 5
4 6 4 6
1 6 4 6

样例输出 Copy

1
3
3
4
3

求每一次操作之后剩下的白色连通块的个数。
初始时,每一块看作一个连通块,利用并查集,每合并一次连通块 -1 。
为了方便,我们可以倒着求。
注意这个题:每次画的那一笔是线段,保证了x1 = x2 或者 y1 = y2。
这样1e7的复杂度就有保证了。

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<map>
#include<queue>
#define ll long long
#define llu unsigned ll
#define pr make_pair
#define pb push_back
using namespace std;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int maxn=1100;
int c[maxn][maxn];
struct node
{
    int x,y;
}a1[maxn*10],a2[maxn*10];
int f[maxn*maxn],ans[maxn*10];
int n,m,q;
int nmax,cnt;

int get(int x,int y)
{
    return x*nmax+y;
}

void init(void)
{
    for(int i=1;i<maxn*maxn;i++)
        f[i]=i;
}

int fi(int x)
{
    if(x!=f[x])
        f[x]=fi(f[x]);
    return f[x];
}


int xx[]={0,0,-1,1};
int yy[]={-1,1,0,0};

bool check(int x,int y)
{
    if(x<1||x>n||y<1||y>m) return false;
    return  true;
}


void doit(int i,int j)
{
    for(int k=0;k<4;k++)
    {
        int nx=i+xx[k];
        int ny=j+yy[k];
        if(!check(nx,ny)) continue;
        if(c[nx][ny]) continue;
        int cc=fi(get(nx,ny));
        int dd=fi(get(i,j));
        if(cc!=dd)
            cnt--,f[cc]=dd;

    }
}

int main(void)
{
    scanf("%d%d%d",&n,&m,&q);
    nmax=max(n,m);
    init();
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d%d%d",&a1[i].x,&a1[i].y,&a2[i].x,&a2[i].y);
        for(int x=a1[i].x;x<=a2[i].x;x++)
            for(int y=a1[i].y;y<=a2[i].y;y++)
                c[x][y]++;
    }

    cnt=n*m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(c[i][j]) cnt--;
            else doit(i,j);
        }
    }

    for(int i=q;i>=1;i--)
    {
        ans[i]=cnt;
        for(int x=a1[i].x;x<=a2[i].x;x++)
        {
            for(int y=a1[i].y;y<=a2[i].y;y++)
            {
                c[x][y]--;
                if(c[x][y]==0)
                {
                    ++cnt;
                    doit(x,y);
                }
            }
        }
    }
    for(int i=1;i<=q;i++)
        printf("%d\n",ans[i]);
    return 0;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值