codeforces1303 F. Number of Components(并查集+添_正序、删_逆序)

F. Number of Components

并查集,每次修改考虑的是这个修改带来的贡献,就是和相邻颜色的对比,如果不考虑先不考虑颜色覆盖,那么添加颜色首先会产生一个新的连通块,然后考虑合并,每合并一次就会减少一个连通块。

题目转化成了对每一种颜色考虑,然后看这个颜色的改变。

对于每种颜色,会有两种:添加一个颜色(正序处理),颜色被覆盖(逆序处理)

而对于颜色被覆盖,只需要逆序考虑即可。每次只考虑贡献,因此最终要求个前缀和。

#include<bits/stdc++.h>

using namespace std;

constexpr int N=310,M=2000010;
int fa[2*M];
int n,m,k;
int id[N][N],idx;
int a[N][N],cur;
int ans[M];
struct node
{
    int x,y;
    int fr,to;
}q[M];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y)
{
    x=find(x),y=find(y);
    if(x==y) return;
    fa[x]=y; cur--;
}
void solve(int x,int y)// 由于边界都设为-1 不用考虑出界问题
{
    if(a[x][y]==a[x-1][y]) merge(id[x][y],id[x-1][y]);
    if(a[x][y]==a[x+1][y]) merge(id[x][y],id[x+1][y]);
    if(a[x][y]==a[x][y-1]) merge(id[x][y],id[x][y-1]);
    if(a[x][y]==a[x][y+1]) merge(id[x][y],id[x][y+1]);
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    
    cin>>n>>m>>k;
    // 询问
    for(int i=1;i<=k;i++)
    {
        int x,y,c;
        cin>>x>>y>>c;
        q[i]={x,y,a[x][y],c};
        a[x][y]=c;
    }
    memset(a,0xff,sizeof a);//边界设为-1
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=0;
    
    //最开始为被询问的编号都默认为0 父亲也是0
    for(int i=1;i<=k;i++)
        if(q[i].fr!=q[i].to)// 如果改变颜色
        {
            cur=1;// 新产生的连通块
            a[q[i].x][q[i].y]=q[i].to;
            id[q[i].x][q[i].y]=++idx;
            fa[idx]=idx;
            solve(q[i].x,q[i].y);// 合并一次 cur--
            ans[i]+=cur;
        }
    // 正着做完后a已经是最终的状态
    idx=0;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) {id[i][j]=++idx;fa[idx]=idx;}
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) solve(i,j);
    // 逆着在做一次
    for(int i=k;i>=1;i--)
        if(q[i].fr!=q[i].to)
        {
            cur=1;
            a[q[i].x][q[i].y]=q[i].fr;
            id[q[i].x][q[i].y]=++idx;
            fa[idx]=idx;
            solve(q[i].x,q[i].y);
            ans[i]-=cur;
        }
    ans[0]=1;//最开始的图连通块个数为1
    for(int i=1;i<=k;i++) ans[i]+=ans[i-1];
    for(int i=1;i<=k;i++) cout<<ans[i]<<'\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值