首页 > 其他分享 >题解:【WC2005】双面棋盘

题解:【WC2005】双面棋盘

时间:2022-08-24 18:00:12浏览次数:83  
标签:双面 题解 查集 个数 一行 WC2005 联通 棋盘

【WC2005】双面棋盘

题目链接

这天做双面棋盘这道题,发现题解里面大多都是 LCT ,对于线段树套并查集的写法思路讲评很少而且不大清晰,因此有了这一篇题解。

维护联通块的数量,很容易联想到使用并查集,考虑暴力,用并查集记录每个点的连通性,最后统计块数即可。但是如果每次进行格子翻转的操作都暴力重构并查集,时间复杂度达到 $O(N^2M)$ ,无法忍受。

不考虑翻转操作,思考从小一点的数据入手,假设这个棋盘只有一行,如:

0 0 1 1 0 0 

容易得出这一行有两个白色联通块,一个黑色联通块。

将棋盘情况扩展到两行,如:

0 0 1 1 0 0
0 1 1 1 0 0

对于第二行,也是两个白色联通块,一个黑色联通块,但是当上下两行连起来时,总数依旧为两个白色联通块,一个黑色联通块。先假设上下两行的联通块个数不互相影响,即总共有四个白色联通块,两个黑色联通块。当第一列相接时,颜色相同,会减少一个白色联通块;当第三列相接时,颜色相同,会减少一个黑色联通块;当第四列相接时,由于 $(1,3)$ 和 $(1,4)$ 联通, $(2,3)$ 和 $(2,4)$ 联通, 刚刚第三列相接标记了 $(1,3)$ 和 $(2,3)$ 联通,所以在并查集中 $(1,4)$ 和 $(2,4)$ 也是联通的,因此块数并不会减少。以此类推。

这时我们可以得出结论:每一行的联通块个数在拼接时并不会改变,多行的联通块个数由相邻的两行的联通块个数和每一列上相邻两格的颜色决定。

因此我们可以以行为单位,维护好区间联通块个数以及最上面一行和最下面一行的连通性信息(叶子节点二者都是它本身)。因为在相接时只有这两行会对最终联通块个数产生影响。

考虑线段树,在叶子节点维护每一行的联通块个数,然后合并每一行,类推到大的区间,最后直接取出线段树结构体中 $1$ 号节点(即整个棋盘)的联通块个数即可。

行与行合并时进行 pushup 操作,直接暴力合并,遍历一遍每一列是否是同一颜色即可。

对于翻转操作,相当于单点修改,我们直接重构这一行的并查集和叶子节点信息,然后同样向上 pushup ,与建树的操作是类似的,因此写代码的时候可以直接对称着写。

时间复杂度 $O(N^2+MN \log^2 N)$。

因为维护的是行的信息,所以代码中的“左”表示“上”,“右”表示“下”。

代码中有更为详细的解释。

个人感觉码风很好?

#include<bits/stdc++.h>
#define MAX 210
using namespace std;

inline int read()
{
    int s=0,w=1;
    char c=getchar();
    while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
    while(isdigit(c)) s=(s<<1)+(s<<3)+(c^48),c=getchar();
    return s*w;
}

int n,m,x,y;
int g[MAX][MAX];  //存棋盘

//并查集,二维压成一维
int fa[MAX*MAX];    
inline int get(int i,int j)   //计算在并查集中的编号 
{
	return (i-1)*n+j;
} 
inline int find(int x)
{
	if(x==fa[x]) return x;
	return fa[x]=find(fa[x]);
} 

//线段树  0为白色   1为黑色
struct Segment_tree
{
	int white,black;   //维护当前这几行的黑白联通块数量
	int l,r;        //维护当前这几行的最上边和最下边的行是什么 
	int ls[MAX],rs[MAX];   //维护当前这几行的最上面一行和最下面一行的连通性(即父节点) 
}t[MAX*4];
inline void pushup(int i)   //暴力合并、父节点更新 
{
	for(int j=1;j<=n;j++)
	{
		//父亲节点的最上行是他左(上)儿子的最上行,父亲节点的最下行是他右(下)儿子的最下行 
		t[i].ls[j]=t[i<<1].ls[j];
		t[i].rs[j]=t[i<<1|1].rs[j];
		//重新计算联通块信息,初始化已经记录下了编号所以不用再get 
		fa[t[i<<1].ls[j]]=t[i<<1].ls[j];
		fa[t[i<<1].rs[j]]=t[i<<1].rs[j];
		fa[t[i<<1|1].ls[j]]=t[i<<1|1].ls[j];
		fa[t[i<<1|1].rs[j]]=t[i<<1|1].rs[j];
	}
	//先假设没有合并,然后再一点点减去 
	t[i].black=t[i<<1].black+t[i<<1|1].black;
	t[i].white=t[i<<1].white+t[i<<1|1].white;
	int mid=(t[i].l+t[i].r)>>1;  //寻找交界的部分 
	for(int j=1;j<=n;j++)
		if(g[mid][j]==g[mid+1][j])   //如果是同一种颜色就要进行合并操作
		{
		//相交的部分为左(上)儿子的最下端和右(下)儿子的最上端 
			int l=find(t[i<<1].rs[j]);   
			int r=find(t[i<<1|1].ls[j]);   
			if(l!=r)
			{
				fa[l]=r;
				if(g[mid][j]==0&&g[mid+1][j]==0) t[i].white--;
				if(g[mid][j]==1&&g[mid+1][j]==1) t[i].black--;
			}
		}
	//更新根节点的信息,一定要让边上为根节点 
	for(int j=1;j<=n;j++)
		t[i].ls[j]=find(t[i].ls[j]),t[i].rs[j]=find(t[i].rs[j]);
}
inline void build(int i,int l,int r)   //建树 
{
	t[i].l=l,t[i].r=r;
	if(l==r)   
	{
		for(int j=1;j<=n;j++)    //初始每一行的每一个点的父亲都是他自己,假设每一个格子都是单独的联通块 
		{
			t[i].ls[j]=t[i].rs[j]=fa[get(l,j)]=get(l,j);
			if(g[l][j]==0) t[i].white++;
			else t[i].black++;
		}
		for(int j=2;j<=n;j++)  //统计这一行的黑白联通块个数,去掉重复计算的联通块 
			if(g[l][j]==g[l][j-1])
			{
				t[i].ls[j]=t[i].rs[j]=fa[get(l,j)]=fa[get(l,j-1)];   //注意是连接到父亲!!! 
				if(g[l][j]==0) t[i].white--;
				else t[i].black--;
			}
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	pushup(i);
	return;
}
inline void change(int i,int to)    //单行修改
{
	if(t[i].l==t[i].r)
	{
		t[i].white=t[i].black=0;   //暴力重新计算,注意清零,剩下的操作与建树是一样的 
		for(int j=1;j<=n;j++)  
		{
			t[i].ls[j]=t[i].rs[j]=fa[get(to,j)]=get(to,j);
			if(g[t[i].l][j]==0) t[i].white++;
			else t[i].black++;
		}
		for(int j=2;j<=n;j++)
			if(g[t[i].l][j]==g[t[i].l][j-1])
			{
				t[i].ls[j]=t[i].rs[j]=fa[get(to,j)]=fa[get(to,j-1)];
				if(g[t[i].l][j]==0) t[i].white--;
				else t[i].black--;
			}
		return;
	}
	if(t[i<<1].r>=to) change(i<<1,to); 
	if(t[i<<1|1].l<=to) change(i<<1|1,to);
	pushup(i);
	return;
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			g[i][j]=read();
	build(1,1,n);
	m=read();
	for(int i=1;i<=m;i++)
	{
		x=read(),y=read();
		g[x][y]=(g[x][y]==0)?1:0;
		change(1,x);
		printf("%d %d\n",t[1].black,t[1].white);
	}
	return 0;
}

标签:双面,题解,查集,个数,一行,WC2005,联通,棋盘
From: https://www.cnblogs.com/LittleTwoawa/p/16621096.html

相关文章

  • has been blocked by CORS policy跨域问题解决
    title:hasbeenblockedbyCORSpolicy跨域问题解决我们在前端调用接口时,浏览器有时候会报错:XXXXformXXXXXhasbeenblockedbyCORSpolicy:No'Access-Control-......
  • CF1715F Crop Squares 题解
    CF1715FCropSquaressolution有一个\(n\timesm\)的长方形,四个角的坐标分别为$(0,0)$,$(0,m)$,$(n,0)$,$(n,m)$。在长方形里面有一个\(1\time......
  • CF838D题解
    很厉害的题。考虑将原本的座位串成一个环,然后加一个节点在\(1\)的前面\(n\)的后面。原问题等价为新节点不被座到的方案数。容易发现所有节点被座到和不被座到的方案......
  • 关于npm ERR! ERESOLVE could not resolve 问题解决
    1、问题描述从代码仓库拉取代码到本地,执行npminstall命令安装项目依赖,提示如下图错误  问题出现的原因由于npm版本问题,npm不同版本库之间命令不兼容。解决办法:执......
  • App 自动化测试实战技巧与经典面试题解析
    ⬇️点击“下方链接”,提升测试核心竞争力!>>更多技术文章分享和免费资料领取移动互联网时代,为了高效应对App多端发布、多版本发布、多机型发布等质量挑战,App自动化测试......
  • [题解]轮流拿牌问题_一道博弈论笔试题(C++)
    题目A和B轮流从一个数组左右两端取数,A先B后,每次取一个数,最终取数总和大者获胜,两人每次都会选择最有利的策略,求获胜者取数的和。思路笔试时遇到的一道算法题,也是博弈论中......
  • ARC099F题解
    被杀了,记录一下好了。对于他那个数组是否相等,直接判断复杂度很高,考虑通过哈希映射之后判断是否相等。对数组的Hash可以类似字符串Hash那样去做。于是判断一个区间是......
  • laravel+mews/captcha 打开页面后的首次验证码总是验证失败的问题解决
    出现问题的原因验证码获取后,还有其他的接口请求,导致验证码的缓存被覆盖(参考文章:LaravelSession遇到的坑)解决办法修改vendor/mews/captcha/src/Captcha.php源码,将......
  • AtCoder Grand Contest 058 部分题目不简要题解
    从这里开始比赛目录ProblemA MakeitZigzag考虑使$1,3,5,7,\cdots,2n-3$这些位置后三个中的最大值在中间,最后再处理一下最后两个位置就行了。Cod......
  • ECfinal2021部分题解
    把赛中没有过的题争取补一下题目链接:https://codeforces.com/gym/103861C:其实,最后每一种字符只有两种状态:1.出现了x,此时就已经知道该字符有多少个了2.没有出现x,那么相......