[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\n",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;
}

原文地址:https://www.cnblogs.com/ZH-comld/p/10165315.html

时间: 2024-10-02 00:46:20

[WC2005]双面棋盘(并查集+分治)的相关文章

【题解】Luogu P4121 [WC2005]双面棋盘

原题传送门 这道题肥肠毒瘤qwqwq,我被卡了qwqwq 这题的正解好像是线段树+并查集,但由于我人丑常数大被卡成了70 #include <bits/stdc++.h> #define N 205 #define getchar nc using namespace std; inline char nc(){ static char buf[100000],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,10

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

[hdu 5354] Bipartite Graph 分治 并查集

题意 给定一张 $n$ 个点, $m$ 条边的无向图. 问删去每个点后, 原图是不是二分图. $1 \le n, m \le {10} ^ 5$ . 分析 一个图是二分图 $\Leftrightarrow$ 图中不存在奇环. 判定一个图是不是二分图, 可以使用并查集, 多维护一个当前点与父亲的关系的量 bond . 删除每一个点, 我们有两种维度: 区间加法, 区间减法. 这里考虑区间加法, 即考虑分治. 由于要支持撤销, 所以使用按秩合并的并查集. 注意按照大小合并... 按深度合并会 TLE

【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树形dp/LCA/树的点分治

题目描述 农夫约翰有N(2≤N≤40000)个农场,标号1到N,M(2≤M≤40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样, 图中农场用F1..F7表示, 每个农场最多能在东西南北四个方向连结4个不同的农场.此外,农场只处在道路的两端.道路不会交叉且每对农场间有且仅有一条路径.邻居鲍伯要约翰来导航,但约翰丢了农场的地图,他只得从电脑的备份中修复了.每一条道路的信息如下: 从农场23往南经距离10到达农场17 从农场1往东经距离7到达农

BZOJ 4025 二分图 分治+并查集

题目大意:给定一张n个点的图,有m条边,T个时间段,每条边只存在于(st,ed]这些时间段,求每个时间段内这个图是否是二分图 分治并查集大法好 定义Solve(x,y,E)为当前处理的区间为[x,y],E为所有存在时间为[x,y]的子集的边的集合 那么对于E中的每一条边(u,v),讨论: 若当前边的存在时间为[x,y],则在并查集上判断是否出现奇环 如果出现,[x,y]内的所有时刻都一定不是二分图,输出答案即可 如果不出现,在并查集中连接(u,v) 否则判断存在时间和mid的关系讨论扔进左区间还

[CDQ分治 并查集] BZOJ 3237 [Ahoi2013]连通图

考虑CDQ分治 把这半边对后半边没有影响的操作做了 然后分治 用并查集维护 开个栈暴力还原 #include<cstdio> #include<cstdlib> using namespace std; inline char nc() { static char buf[100000],*p1=buf,*p2=buf; if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; } re

【openjudge】C15C Rabbit&#39;s Festival CDQ分治+并查集

题目链接:http://poj.openjudge.cn/practice/C15C/ 题意:n 点 m 边 k 天.每条边在某一天会消失(仅仅那一天消失).问每一天有多少对点可以相互到达. 解法:开始不会做,参考的YYN的题解:http://blog.csdn.net/u013368721/article/details/45725181 学习了这种CDQ加并查集的做法,可以说是非常的巧妙了.复杂度可以保证在:O(KlogklogK)的范围. //CDQ + DSU #include <bit

[BZOJ4025]二分图(线段树分治,并查集)

4025: 二分图 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2191  Solved: 800[Submit][Status][Discuss] Description 神犇有一个n个节点的图.因为神犇是神犇,所以在T时间内一些边会出现后消失.神犇要求出每一时间段内这个图是否是二分图.这么简单的问题神犇当然会做了,于是他想考考你. Input 输入数据的第一行是三个整数n,m,T. 第2行到第m+1行,每行4个整数u,v,start,end

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并