ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

题解:【WC2005】双面棋盘

2022-08-24 18:00:09  阅读:176  来源: 互联网

标签:两行 双面 题解 查集 个数 一行 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,联通,棋盘
来源: https://www.cnblogs.com/LittleTwoawa/p/16621096.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有