P4121 [WC2005] 双面棋盘

发布时间 2023-09-08 10:28:54作者: NBest

2023-07-18 20:48:35

简化题意:

给你一个n*n的只有黑白两种颜色的棋盘,每次修改把某格的黑白互换,求每次修改后黑白各自的连通块个数。

解法

将棋盘转化成n行,每行单独处理,处理出黑色和白色分别的连通块个数(可用并查集)。
开一个n的线段树,每个叶子节点表示一行。
合并过程中,先将黑白的连通块数量相加作为合并后的连通块数量,再用并查集将颜色相同但是非同一个指向的并查集合并并减去一个连通块个数。
每次修改都重新修改对应行的并查集数组。
修改行和合并行复杂度都是 \(O(n \log n)\)
单次修改复杂度\(O(n\log ^2n)\)
总复杂度 $ O(nm\log ^2 n)\(,\)n\leq 200\(,\)m \leq 10000$,可过。

遇到的问题1:

并查集得开二维,但是如果直接开二维组存很麻烦而且容易出错,所以用了题解的一个小技巧,把二维压成一维,这样和一般的并查集没有什么区别。

遇到的问题2:

修改值后只修改该行的值不能正解,原因大概是其他还有的地方指向之前未修改过的行,因此要在pushup里将有关的fa[]全修改掉(直接全局修改会超时)。

待解决的小问题:

思考为什么直接重开fa[]数组会WA,而新建ls[],rs[]数组记录上下两行的数据对应的并查集终点并在中途修改可以通过此题。

Code

int n,m;
int fa[40004];
inline int gl(int x,int y){
    return n*(x-1)+y;
}
inline int find(int x){
    if(x==fa[x])return x;
    return fa[x]=find(fa[x]);
}
struct line{
    int up[205],down[205],bl,wh,hup,hdown;//h 针对单行位置
    int ls[205],rs[205];
    void recreate(){ //单行暴力修改
        bl=wh=0;
        for(int i=1;i<=n;i++){
            ls[i]=rs[i]=fa[gl(hup,i)]=gl(hup,i);
            bl+=up[i];
            wh+=up[i]^1;
        }
        for(int i=2;i<=n;i++){
            if(up[i]==up[i-1]){
                ls[i]=rs[i]=fa[find(gl(hup,i))]=find(gl(hup,i-1));
                bl-=up[i];
                wh-=up[i]^1;
            }
        }
    }
}tr[806];
inline void pushup(int root){
    tr[root].bl=tr[root<<1].bl+tr[root<<1|1].bl;
    tr[root].wh=tr[root<<1].wh+tr[root<<1|1].wh;
    for(int i=1;i<=n;i++){
        tr[root].ls[i]=tr[root<<1].ls[i];
        tr[root].rs[i]=tr[root<<1|1].rs[i];
        fa[tr[root<<1].ls[i]]=tr[root<<1].ls[i];
        fa[tr[root<<1].rs[i]]=tr[root<<1].rs[i];
        fa[tr[root<<1|1].ls[i]]=tr[root<<1|1].ls[i];
        fa[tr[root<<1|1].rs[i]]=tr[root<<1|1].rs[i];
        tr[root].up[i]=tr[root<<1].up[i];
        tr[root].down[i]=tr[root<<1|1].down[i];
    }
    tr[root].hup=tr[root<<1].hup;
    tr[root].hdown=tr[root<<1|1].hdown;
    for(int i=1;i<=n;i++){
        int upfa=find(tr[root<<1].rs[i]),downfa=find(tr[root<<1|1].ls[i]);
        if(tr[root<<1].down[i]==tr[root<<1|1].up[i]&&upfa!=downfa){
            fa[upfa]=downfa;
            tr[root].bl-=tr[root<<1].down[i];
            tr[root].wh-=tr[root<<1].down[i]^1;
        }
    }
    for(int i=1;i<=n;i++){
        tr[root].ls[i]=find(tr[root].ls[i]),tr[root].rs[i]=find(tr[root].rs[i]);
    }
}
void build(int root,int l,int r){
    if(l==r){
        for(int i=1;i<=n;i++){
            tr[root].up[i]=tr[root].down[i]=read();
        }
        tr[root].hup=tr[root].hdown=l;
        tr[root].recreate();
        //printf("line %d: bl:%d wh:%d\n",l,tr[root].bl,tr[root].wh);
        return;
    }
    build(root<<1,l,mid),build(root<<1|1,mid+1,r);
    pushup(root);
    //printf("line %d-%d: bl:%d wh:%d\n",l,r,tr[root].bl,tr[root].wh);
}
void update(int root,int l,int r,int x,int y){
    if(l==r){
        tr[root].up[y]^=1;
        tr[root].down[y]^=1;
        tr[root].recreate();
        //printf("line %d: bl:%d wh:%d\n",l,tr[root].bl,tr[root].wh);
        return;
    }
    if(mid>=x)update(root<<1,l,mid,x,y);
    else update(root<<1|1,mid+1,r,x,y);
    pushup(root);
    //printf("line %d-%d: bl:%d wh:%d\n",l,r,tr[root].bl,tr[root].wh);
}
int main(){
    n=read();
    build(1,1,n);
    m=read();
    while(m--){
        int x=read(),y=read();
        update(1,1,n,x,y);
        printf("%d %d\n",tr[1].bl,tr[1].wh);
    }
}