首页 > 其他分享 >洛谷P3300 [SDOI2013] 城市规划 题解

洛谷P3300 [SDOI2013] 城市规划 题解

时间:2023-10-10 16:22:06浏览次数:53  
标签:fx 洛谷 vis int 题解 fa SDOI2013 fy return

[SDOI2013] 城市规划

题意:给你一个 \(6 \times n\) 的网格题,单点修改,询问区间联通块数,\(n \le 10^5\)。

解:看起来就很显然的一道题......线段树每个点用一个 ufs 维护连通性;

我为了方便思考把图转成横着的了。

写起来真是毒瘤......

重点在于:

\(\bullet\) 1、建立叶节点。

\(\bullet\) 2、合并两个子节点。

\(\bullet\) 3、把新的并查集的中间两列压掉。

第一步,这个就直接枚举 \(merge\) 就完事了。

第二步,把两个 2 列的子并查集复制到当前节点的 4 列的并查集上。注意右边那个并查集,\(fa\) 全部要加 \(2m\),因为下标加了 \(2m\)。

然后枚举中间两列 \(merge\)。这样也做完了。

第三步,我们只要保留两边的两列即可。注意到有可能有两边元素的代表元在中间,我们使用左偏树技巧,切换代表元到它自己,注意 \(vis\) 也要变。

然后把两边的 \(vis\) 和 \(fa\) 复制一份出来,用以查询代表元。

然后把前两列 init,然后枚举每个元素,跟它的代表元 \(merge\)。注意这一步会对连通块总数 tot 造成改变,最后复原即可。

最后把前两列的 \(vis\) 和 \(lk\) 从复制的那里拿来用即可。

注意 \(vis\)(当前联通块是否有建筑物)要继承代表元的,\(lk\)(能否与两边连通)直接从对应下标继承,这两个不一样!

具体实现看代码吧......

#include<bits/stdc++.h>
using namespace std;
#define ck(x) ((x)=='|'||(x)=='+')
const int N=1e5+100;
const int M=24;
int n,m,q,x,y;
char c[2];
int exfa[M],exvis[M],exlk[M];
inline int read(){
	char c=getchar();int f=1,x=0;
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int exfind(int x){
	if(exfa[x]==x) return x;
	else return exfa[x]=exfind(exfa[x]);
}
inline int trans(int x){
	return x<m?x:x-(m<<1);
}
struct node{
	int fa[M],tot;
	bitset<M> lk,vis;
	int find(int x){
		if(fa[x]==x) return x;
		else return fa[x]=find(fa[x]);
	}
	inline void merge(int x,int y){
		int fx=find(x),fy=find(y);
		if(fx==fy) return ;
		fa[fy]=fx;
		tot-=(vis[fx]&&vis[fy]);
		if(vis[fy]) vis.set(fx);
		return ;
	}
	node(){}
	node(char *a){
		tot=0;
		for(int i(0);i<m;i++){
			fa[i+m]=fa[i]=i;
			if(a[i]=='O'){
				vis.set(i);
				vis.set(i+m);
				lk.set(i);
				lk.set(i+m);
				++tot;
			}else if(a[i]=='.'){
				vis.reset(i);
				vis.reset(i+m);
				lk.reset(i);
				lk.reset(i+m);
			}else if(a[i]=='-'||a[i]=='+'){
				vis.reset(i);
				vis.reset(i+m);
				lk.set(i);
				lk.set(i+m);
			}
		}
		for(int i(1);i<m;i++){
			if(ck(a[i-1])&&ck(a[i])) merge(i-1,i);
			else if((ck(a[i-1])&&a[i]=='O')||(ck(a[i])&&a[i-1]=='O')) merge(i-1,i);
			else if(a[i]=='O'&&a[i-1]=='O') merge(i-1,i);
		}
	}
	inline void update(){
		for(int i(0);i<m;i++){
			int t(find(i));
			fa[i]=fa[t]=i;
			vis[i]=vis[t];
			t=find(i+3*m);
			fa[i+3*m]=fa[t]=i+3*m;
			vis[i+3*m]=vis[t];
		}
		memcpy(exfa,fa,sizeof(fa));
		for(int i(0);i<m;i++){
			exvis[i]=vis[i];
			exvis[i+3*m]=vis[i+3*m];
			exlk[i]=lk[i];
			exlk[i+3*m]=lk[i+3*m];
		}
		int temp=tot;
		for(int i(0);i<m;i++){
			fa[i]=i;
			fa[i+m]=i+m;
		}
		for(int i(0);i<m;i++){
			merge(i,trans(exfind(i)));
			merge(i+m,trans(exfind(i+3*m)));
		}
		for(int i(0);i<m;i++){
			vis[i]=exvis[exfind(i)];
			vis[i+m]=exvis[exfind(i+m*3)];
			lk[i]=exlk[i];
			lk[i+m]=exlk[i+m*3];
		}
		tot=temp;
		return ;
	}
	inline node merge(const node &w)const{
		node ans;
		for(int i(0);i<m;i++){
			ans.fa[i]=fa[i];
			ans.lk[i]=lk[i];
			ans.vis[i]=vis[i];
			ans.fa[i+m]=fa[i+m];
			ans.lk[i+m]=lk[i+m];
			ans.vis[i+m]=vis[i+m];
			ans.fa[i+2*m]=w.fa[i]+2*m;
			ans.lk[i+2*m]=w.lk[i];
			ans.vis[i+2*m]=w.vis[i];
			ans.fa[i+3*m]=w.fa[i+m]+2*m;
			ans.lk[i+3*m]=w.lk[i+m];
			ans.vis[i+3*m]=w.vis[i+m];
		}
		ans.tot=tot+w.tot;
		for(int i(0);i<m;i++){
			if(ans.lk[i+m]&&ans.lk[i+2*m]){
				ans.merge(i+m,i+2*m);
			}
		}
		ans.update();;
		return ans;
	}
}tr[N<<2];
char str[N][6];
void build(int l,int r,int now){
	if(l==r){
		tr[now]=node(str[r]);
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,now<<1);
	build(mid+1,r,now<<1|1);
	tr[now]=tr[now<<1].merge(tr[now<<1|1]);
	return ;
}
void change(int p,int l,int r,int now){
	if(l==r){
		tr[now]=node(str[r]);
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid) change(p,l,mid,now<<1);
	else change(p,mid+1,r,now<<1|1);
	tr[now]=tr[now<<1].merge(tr[now<<1|1]);
	return ;
}
node get_sum(int x,int y,int l,int r,int now){
	if(x<=l&&y>=r) return tr[now];
	int mid=(l+r)>>1;
	if(y<=mid) return get_sum(x,y,l,mid,now<<1);
	if(mid<x) return get_sum(x,y,mid+1,r,now<<1|1);
	node temp(get_sum(x,y,l,mid,now<<1));
	temp=temp.merge(get_sum(x,y,mid+1,r,now<<1|1));
	return temp; 
}
signed main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		scanf("%s",str[i]);
		for(int j=0;j<m;j++){
			if(str[i][j]=='-') str[i][j]='|';
			else if(str[i][j]=='|') str[i][j]='-';
		}
	}
	build(1,n,1);
	q=read();
	while(q--){
		scanf("%s",c);
		x=read();y=read();
		if(c[0]=='C'){
			scanf("%s",c);
			if(c[0]=='-') c[0]='|';
			else if(c[0]=='|') c[0]='-';
			str[x][y-1]=c[0];
			change(x,1,n,1);
		}else{
			node temp=get_sum(x,y,1,n,1);
			printf("%d\n",temp.tot);
		}
	}
	return 0;
}

( PS :一开始没开 O2 的时候 TLE 了,只有 50 分,吸氧以后才过的,希望哪位 dalao 可以找到更好的思路

标签:fx,洛谷,vis,int,题解,fa,SDOI2013,fy,return
From: https://www.cnblogs.com/xuantianhao/p/17754997.html

相关文章

  • 【ABC320C】题解
    AtCoderBeginnerContest320ProblemC-SlotStrategy2(Easy)题解题目简述给定\(3\)个长度为\(m\)的转盘,转动过后三个转盘分别可以在不同的时间停下,求停下时所有转盘都显示相同数字的最小时间。思路由于这题\(m\le100\),数据较水,所以可以先把每个数列都复制\(......
  • 【ABC320D】题解
    AtCoderBeginnerContest320ProblemD-RelativePosition题解题目保证不矛盾,就可以直接vector建图,然后dfs一遍,边权为\((w_x,w_y)\)表示坐标的差,从\(u=1\)开始搜索,设点\(u,v\)有一条无向边,\(v_x=u_x+w_x,v_y=u_y+w_y\),最后如果没有被标记过的话(也就是没有......
  • 【题解】Fibonacci-ish II
    传送门题目分析根据题目范围\(n\le30000\)并且此题可以离线维护这个很恶心的东西,所以我们考虑莫队。由于要求访问到任意一个区间都要求知道它有序之后的序列,所以这个东西可以用权值线段树维护。因此,此题正解是莫队+权值线段树。我们分类讨论一下加上一个数,删除一个数对答案......
  • 题解 P3894【[GDOI2014] 传送】
    难倒不难,128MB的空间限制快恶心死我了。我们设\(d_{u_0,u_1}\)表示到节点\((u_0,u_1)\)距离最近的叶子的距离,这个可以很容易换根DP求出。设\(p_{u_0}\)表示树\(u_0\)中距离最近的两个叶子的距离。设\(dis(u_0,u_1,v_0,v_1)\)(\(u_0=v_0\))表示树中两个节点\(u_1\)和......
  • 【ABC322C】题解
    AtCoderBeginnerContest322ProblemC-Festival题解Meaning-题意简述给定\(N\)和\(M\),还有\(M\)个正整数\(a_1\sima_n\),对于每个\(i\len\),求出\(a\)中第一个大于等于\(i\)的整数和\(i\)的差。Solution-题解思路题目保证\(a\)数组单增,所以就可......
  • 【LG-P7617】题解
    题解思路不用关心每个数的每一位是什么、哪几位相同,我们只需记录每个数出现了哪几个数字,可以使用类似于状态压缩的思想记录每个数的状压形式,比如一个数为\((4)_{10}\),那么他的状态压缩形式为\((00001)_2\)。当两个数在状态压缩表示下有一位相同,我们就认为这两个数是一对,每个......
  • 【ABC322D】题解
    AtCoderBeginnerContest322ProblemD-Polyomino题解Meaning-题意简述给定三个字符矩阵,求它们能不能拼在一起变成一个\(4\times4\)的全部是#的矩阵。Solution-题解思路大模拟。说简单也不简单,很复杂;但是说难呢,又不难。思路:搜索每一个矩阵的状态。0x001旋......
  • P1220 关路灯 题解
    Description给定\(n\)个点的位置\(a_i\)和每秒的花费\(b_i\),你的初始位置是\(s\),你删掉一个点的时间为\(0\)秒,走\(1\)个单位长度的时间是\(1\)秒。请你确定一种关灯顺序,使得所有点的最终花费最小(删掉点后这个点不会再花费)。Solution每删掉一个点,有两种选择:继续往前......
  • 汉诺塔(河内塔)题解
    汉诺塔(河内塔)题解我们定义\(T_n\)为根据规则将\(n\)个圆盘从一根柱子移动到另一根柱子的最少移动步数,按照这样的定义,本道题的答案实际上就是\(T_n\)。通过手动模拟,可得到\(T_1=1,T_2=3\)。同时显然有\(T_0=0\),即表示\(0\)个圆盘根本无需做任何移动。接着我们开始考虑......
  • CF1142D Foreigner题解
    CF1142DForeigner题解前言:题目含义真的好难理解呜呜。遇到的dp套dp的第三题,所以深入进行了理解。参考博文:https://www.cnblogs.com/AWhiteWall/p/16479483.html题意简化:先定义了不充分。首先数字$[1,9]$都不充分,注意没有$0$。当这个数字(设为$x$)大于等于$10$时......