P4121 [WC2005]双面棋盘(线段树+并查集)

传送门

题意:给你一个棋盘,棋盘格子有颜色,每次都会单点修改,问你修改后的白色和黑色联通块的数量。

题解:并查集是可以用来求联通块数量的,如果没有修改就是一个简单的带权并查集,但是由于有修改并且在棋盘上,所以如果只是并查集是不足以完成的,然后注意题目限制1 ≤ n≤ 200,而且每次只是单点修改,修改直接影响的只有当前列(将棋盘按列分割开)对于其他列是没有影响的,然后就只会在相邻行合并时产生影响,通过线段树按行建树后,修改操作只会影响logm层,所以每次对于修改直接暴力维护并查集,时间复杂度为nlogm,由于n很小,复杂度完全能够接受,对于答案只需要O(1)输出根节点储存的信息

AC代码:


#include<bits/stdc++.h>
#define ll long long
using namespace std;
int read(){
    char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
    if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
        x=x*10+c-'0';return x*y;
}
const int maxn=2e2+5;
struct node{
    int L[maxn],R[maxn];
    int ans[2];
}tree[maxn*4];
int fa[maxn*maxn],mp[maxn][maxn],n;
inline int getid(int x,int y){
    return (x-1)*n+y;
}
inline int find(int x){
    if(x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}
inline void pushup(node &ci,node ai,node bi,int m){
    for(int a=1;a<=n;a++){
        ci.L[a]=ai.L[a];
        ci.R[a]=bi.R[a];
        fa[ai.L[a]]=ai.L[a];
        fa[ai.R[a]]=ai.R[a];
        fa[bi.L[a]]=bi.L[a];
        fa[bi.R[a]]=bi.R[a];
    }
    ci.ans[0]=ai.ans[0]+bi.ans[0];
    ci.ans[1]=ai.ans[1]+bi.ans[1];
    for(int a=1;a<=n;a++){
        if(mp[a][m]==mp[a][m+1]){
            int fi=find(ai.R[a]),fj=find(bi.L[a]);
            if(fi!=fj){
                fa[fi]=fj;
                ci.ans[mp[a][m]]--;
            }
        }
    }
    for(int a=1;a<=n;a++){
        ci.L[a]=find(ci.L[a]);
        ci.R[a]=find(ci.R[a]);
    }
}
inline void build(int l,int r,int k){
    if(l==r){
        tree[k].ans[0]=tree[k].ans[1]=0;
        for(int a=1;a<=n;a++)
            tree[k].L[a] = tree[k].R[a] = getid(a, l),tree[k].ans[mp[a][l]]++;
        for(int a=2;a<=n;a++)
            if(mp[a][l]==mp[a-1][l])
                tree[k].L[a]=tree[k].R[a]=tree[k].L[a-1],tree[k].ans[mp[a][l]]--;
            return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,k<<1);
    build(mid+1,r,k<<1|1);
    pushup(tree[k],tree[k<<1],tree[k<<1|1],mid);
}
inline void modify(int l,int r,int k,int l1){
    if(l==r){
        tree[k].ans[0]=tree[k].ans[1]=0;
        for(int a=1;a<=n;a++)
            tree[k].L[a] = tree[k].R[a] = getid(a, l),tree[k].ans[mp[a][l]]++;
        for(int a=2;a<=n;a++)
            if(mp[a][l]==mp[a-1][l])
                tree[k].L[a]=tree[k].R[a]=tree[k].L[a-1],tree[k].ans[mp[a][l]]--;
        return ;
    }
    int mid=(l+r)>>1;
    if(mid>=l1) modify(l,mid,k<<1,l1);
    else modify(mid+1,r,k<<1|1,l1);
    pushup(tree[k],tree[k<<1],tree[k<<1|1],mid);
}
int main( ){
    n=read();
    for(int a=1;a<=n;a++) for(int b=1;b<=n;b++) mp[a][b]=read();
    build(1,n,1);
    int q=read();
    while(q--){
        int x=read(),y=read();
        mp[x][y]^=1;
        modify(1,n,1,y);
        printf("%d %d\n",tree[1].ans[1],tree[1].ans[0]);
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值