[WC2005]双面棋盘(并查集+分治)

Posted zh-comld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[WC2005]双面棋盘(并查集+分治)相关的知识,希望对你有一定的参考价值。

题目描述

技术分享图片

题解

唉,还是码力不行,写了一个多小时发现想错了又重构了一个多小时。

这道题意图很显然,动态维护联通块,有一个经典做法就是用LCT维护按照删除时间维护的最大生成树。

网上还有一种神奇的做法,线段树套并查集,蒟蒻表示不懂。。

这道题可以利用并查集操作可以撤销这种性质来做。

线段树分治

线段树分治可以分两种情况,操作之间独立和操作之间不独立。

操作之间独立意味着我先完成哪个操作就可以,例如找最优点,有一道例题

还有一种是操作之间是可以相互影响的,比如说这道题,连通性这种东西和我加的每一条边都有关。

我们可以按时间分治,先离线找出每条边出现的时间段,把这些时间段加入线段树中,然后在线段树上dfs,进入节点时把所有边加入,删除时栈序撤销来的时候的操作(因为线段树dfs的过程也是压栈弹栈的过程,所以我们可以准确撤销操作),然后在根节点统计答案,联通块的个数为点数-边数,点数这种东西我们可以直接离线维护(我一开始傻了)。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 40002
#define maxn 209
using namespace std;
int n,id[maxn][maxn],f[N],tot,ls[N<<1],rs[N<<1],b[N],bl,w[N],wl,deep[N],num,last[N<<1],m,root,a[maxn][maxn],bian;
int tag[maxn][maxn][4],anti[4],color[N<<1];
const int dx[4]={0,1,-1,0};
const int dy[4]={1,0,0,-1};
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c==-)f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
int find(int x){return f[x]==x?x:find(f[x]);}
struct node{int id,co;};
struct rbs{int dep,root,link,co;};
struct node2{int x,y;};
vector<node>vec[N<<1];
vector<rbs>zh[N<<1];
node2 linko[N<<1];
void upd(int &cnt,int l,int r,int L,int R,int x,int y){
    if(!cnt)cnt=++tot;
    if(l>=L&&r<=R){vec[cnt].push_back(node{x,y});return;}
    int mid=(l+r)>>1;
    if(mid>=L)upd(ls[cnt],l,mid,L,R,x,y);
    if(mid<R)upd(rs[cnt],mid+1,r,L,R,x,y);
}
void solve(int &cnt,int l,int r){
    if(!cnt)cnt=++tot;
    //cout<<l<<"  ****  "<<r<<endl;
    for(int i=0;i<vec[cnt].size();++i){
        int id=vec[cnt][i].id,co=vec[cnt][i].co;
        int x=linko[id].x,y=linko[id].y;
        int xx=find(x),yy=find(y);
        if(xx!=yy){
            if(co)bl++;else wl++;
            if(deep[xx]<deep[yy])swap(xx,yy);
            zh[cnt].push_back(rbs{deep[xx],xx,yy,co});
            f[yy]=xx;deep[xx]=max(deep[xx],deep[yy]+1);
          }
    }    
//    cout<<b[l]<<" "<<w[l]<<" "<<bl<<" "<<wl<<endl;
    if(l==r){if(l)printf("%d %d
",b[l]-bl,w[l]-wl);}
    else{
    int mid=(l+r)>>1;
    solve(ls[cnt],l,mid);
    solve(rs[cnt],mid+1,r);
    }
    while(zh[cnt].size()){
        rbs x=zh[cnt].back();zh[cnt].pop_back();
        deep[x.root]=x.dep;f[x.link]=x.link;
        if(x.co)bl--;else wl--;
    }
}
int main(){
    anti[0]=3;anti[3]=0;anti[2]=1;anti[1]=2;
    n=rd();int x,y;
    for(int i=1;i<=n;++i)
      for(int j=1;j<=n;++j){
      a[i][j]=rd(),id[i][j]=++num;
      if(a[i][j])b[0]++;else w[0]++;
    }
    for(int i=1;i<=num;++i)f[i]=i,deep[i]=1;
    memset(last,-1,sizeof(last));
    for(int i=1;i<=n;++i)
      for(int j=1;j<=n;++j)
          for(int k=0;k<2;++k){
              int xx=i+dx[k],yy=j+dy[k];
              if(!id[xx][yy])continue;
              tag[i][j][k]=tag[xx][yy][anti[k]]=++bian;
              if(a[i][j]==a[xx][yy])last[bian]=0,color[bian]=a[i][j];
              linko[bian]=node2{id[i][j],id[xx][yy]};
          }
    m=rd();
    for(int i=1;i<=m;++i){
        x=rd();y=rd();b[i]=b[i-1];w[i]=w[i-1];
        if(a[x][y])b[i]--,w[i]++;else b[i]++,w[i]--;
        for(int k=0;k<4;++k){
          int xx=x+dx[k],yy=y+dy[k],_id=tag[x][y][k];
          if(!_id)continue; 
          if(a[x][y]==a[xx][yy])
            upd(root,0,m,last[_id],i-1,_id,a[x][y]),last[_id]=-1;
          else last[_id]=i,color[_id]=a[xx][yy];
        }
        a[x][y]^=1;
    }
    for(int i=1;i<=n;++i)
      for(int j=1;j<=n;++j)
        for(int k=0;k<2;++k){
          int xx=i+dx[k],yy=j+dy[k],_id=tag[i][j][k];
          if(~last[_id])upd(root,0,m,last[_id],m,_id,color[_id]);
        }
    solve(root,0,m);
    return 0; 
}

以上是关于[WC2005]双面棋盘(并查集+分治)的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ1453[Wc]Dface双面棋盘 线段树+并查集

[Wc]Dface双面棋盘

BZOJ1453: [Wc]Dface双面棋盘

1453: [Wc]Dface双面棋盘 (线段树+并茶几)

bzoj1453: [Wc]Dface双面棋盘

bzoj 1453 双面棋盘