码迷,mamicode.com
首页 > 其他好文 > 详细

「Luogu P3300 [SDOI2013]城市规划」

时间:2020-05-26 15:36:18      阅读:62      评论:0      收藏:0      [点我收藏+]

标签:ddl   题目   ==   左右   clu   连通块   amp   维护   程序   

题目大意

给出一个 \(n\times m\) 的图,图中有 +,|,- 表示联通,求一段 \((l,m)\sim(r,m)\) 中有多少包含 O 的连通块.

分析

发现 \(m\) 很小,所以就往 \(m\) 去想做法,考虑有线段树来维护一段区间内的连通性,对于两段区间的合并可以直接暴力搞.

考虑在线段树的节点上维护以下信息:

sum:表示当前区间内有多少个带建筑物的连通块.

l[],r[]:两个字符数组,表示当前区间最上面和最下面的那一行字符.

father[]:一个并查集,维护最上面和最下面各个位置之间的连通性

building[]:维护每个联通块内是否有建筑物.

合并的时候就将两块的并查集数组弄成一个并查集,对于中间可以连接的位置直接在这个并查集上连接,最后放回节点上.

感觉这个东西还是挺好想的,但是写起来是真的亿堆细节.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=1e5+5;
int n,m,q;
char arr[MAXN][7];
bool CheckHL(char ch)//判断这个字符是否可以左右连接
{
	if(ch==‘+‘||ch==‘O‘||ch==‘-‘)
	{
		return 1;
	}
	return 0;
}
bool CheckLR(char ch)//判断这个字符是否可以上下连接
{
	if(ch==‘+‘||ch==‘O‘||ch==‘|‘)
	{
		return 1;
	}
	return 0;
}
struct SegmentTree//线段树部分
{
	int sum;
	char l[7],r[7];//最上面和最下面的行
	int father[13];//一个并查集1~m为上面行,m+1~2m是下面行
	bool building[13];//记录连通块内是否有O
	int Find(int now)//并查集部分
	{
		if(father[now]==now)
		{
			return now;
		}
		return father[now]=Find(father[now]);
	}
	void Add(int a,int b)
	{
		building[Find(a)]|=building[Find(b)];
		father[Find(a)]=Find(b);
	}
}sgt[MAXN*4],for_merge,null/*一个空节点*/;
int father[30],f[30];
bool building[30];
int Find(int now)
{
	if(father[now]==now)
	{
		return now;
	}
	return father[now]=Find(father[now]);
}
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
SegmentTree Merge(SegmentTree lson,SegmentTree rson)//合并两段区间
{
	if(lson.father[1]==0)//如果有一段区间不存在就返回另一段区间
	{
		return rson;
	}
	if(rson.father[1]==0)
	{
		return lson;
	}
	REP(i,1,m)//先对合并时用的标记赋初值
	{
		for_merge.l[i]=lson.l[i];
		for_merge.r[i]=rson.r[i];
		for_merge.building[i]=
		for_merge.building[i+m]=0;
        for_merge.father[i]=i;
        for_merge.father[i+m]=i+m;
	}
	for_merge.sum=lson.sum+rson.sum;//开始时联通块个数为两区间连通块个数之和
	REP(i,1,m)
	{
        //对于两区间用同一个并查集维护连通性更加方便
		father[i]=lson.Find(lson.father[i]);
		father[i+m]=lson.Find(lson.father[i+m]);
		father[i+m*2]=rson.Find(rson.father[i])+m*2;
		father[i+m*3]=rson.Find(rson.father[i+m])+m*2;
		building[i]=lson.building[father[i]];
		building[i+m]=lson.building[father[i+m]];
		building[i+m*2]=rson.building[father[i+m*2]-2*m];
		building[i+m*3]=rson.building[father[i+m*3]-2*m];
	}
	REP(i,1,m)
	{
		if(CheckLR(lson.r[i])&&CheckLR(rson.l[i])&&Find(i+m)^Find(i+2*m))//如果在中间连接的位置可以连接且属于不同的连通块
		{
			if(building[Find(i+m)]&&building[Find(i+2*m)])//如果两连通块内都有O,那么总个数就要减一
			{
				for_merge.sum--;
			}
			building[Find(i+2*m)]|=building[Find(i+m)];//记录连通块内是否有O
			father[Find(i+m)]=Find(i+2*m);
		}
	}
    //将并查集内的东西放到节点上
	REP(i,1,m)
	{
		if(!f[Find(i)])//判断前是否出现过这个连通块
		{
			for_merge.father[i]=f[Find(i)]=i;//这里必须写成n,可以理解一下并查集
			for_merge.building[i]=building[Find(i)];
		}
		else
		{
			for_merge.father[i]=f[Find(i)];
		}
	}
	REP(i,m+1,m*2)
	{
		if(!f[Find(2*m+i)])
		{
			for_merge.father[i]=f[Find(2*m+i)]=i;
			for_merge.building[i]=building[Find(2*m+i)];
		}
		else
		{
			for_merge.father[i]=f[Find(2*m+i)];
		}
	}
	return for_merge;
}
void PushUp(int now)
{
	sgt[now]=Merge(sgt[LSON],sgt[RSON]);
}
void Build(int now=1,int left=1,int right=n)
{
	if(left==right)//对于叶节点特别处理
	{
		REP(i,1,m)//赋初值
		{
			sgt[now].l[i]=
			sgt[now].r[i]=
			arr[left][i];
			sgt[now].building[i]=
			sgt[now].building[i+m]=0;
		}
		REP(i,1,m)
		{
			sgt[now].father[i]=
			sgt[now].father[i+m]=i;
		}
		REP(i,2,m)//如果左右可以相连就合并一下
		{
			if(CheckHL(arr[right][i-1])&&CheckHL(arr[right][i]))
			{
				sgt[now].Add(sgt[now].Find(i-1),sgt[now].Find(i));
			}
		}
		REP(i,1,m)//如果联通快内有O就打上标记
		{
			if(arr[left][i]==‘O‘)
			{
				sgt[now].building[sgt[now].Find(i)]=1;
			}
		}
		sgt[now].sum=sgt[now].building[sgt[now].Find(1)];//统计有几个有O的连通块
		REP(i,2,m)
		{
			if(sgt[now].Find(i)^sgt[now].Find(i-1))
			{
				sgt[now].sum+=sgt[now].building[sgt[now].Find(i)];
			}
		}
		return;
	}
	Build(LEFT);
	Build(RIGHT);
	PushUp(now);
}
void Updata(int place,int now=1,int left=1,int right=n)
{
	if(place<left||right<place)
	{
		return;
	}
	if(left==right)//对于线段树叶节点的修改和建树相同
	{	
		REP(i,1,m)
		{
			sgt[now].l[i]=
			sgt[now].r[i]=
			arr[left][i];
			sgt[now].building[i]=
			sgt[now].building[i+m]=0;
		}
		REP(i,1,m)
		{
			sgt[now].father[i]=
			sgt[now].father[i+m]=i;
		}
		REP(i,2,m)
		{
			if(CheckHL(arr[right][i-1])&&CheckHL(arr[right][i]))
			{
				sgt[now].Add(sgt[now].Find(i-1),sgt[now].Find(i));
			}
		}
		REP(i,1,m)
		{
			if(arr[left][i]==‘O‘)
			{
				sgt[now].building[sgt[now].Find(i)]=1;
			}
		}
		sgt[now].sum=sgt[now].building[sgt[now].Find(1)];
		REP(i,2,m)
		{
			if(sgt[now].Find(i)^sgt[now].Find(i-1))
			{
				sgt[now].sum+=sgt[now].building[sgt[now].Find(i)];
			}
		}
		return;
	}
	Updata(place,LEFT);
	Updata(place,RIGHT);
	PushUp(now);
}
SegmentTree Query(int now_left,int now_right,int now=1,int left=1,int right=n)//查询也很简单
{
	if(now_right<left||right<now_left)
	{
		return null;
	}
	if(now_left<=left&&right<=now_right)
	{
		return sgt[now];
	}
	return Merge(Query(NOW,LEFT),Query(NOW,RIGHT));
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
int main()//主程序就很简单了
{
	scanf("%d%d",&n,&m);
	REP(i,1,n)
	{
		REP(j,1,m)
		{
			cin>>arr[i][j];
		}
	}
	Build();
	scanf("%d",&q);
	char opt,ch;
	int left,right,x,y;
	REP(i,1,q)
	{
		cin>>opt;
		if(opt==‘Q‘)
		{
			scanf("%d%d",&left,&right);
			printf("%d\n",Query(left,right).sum);
		}
		if(opt==‘C‘)
		{
			scanf("%d%d",&x,&y);
			cin>>ch;
			arr[x][y]=ch;
			Updata(x);
		}
	}
	return 0;
}

「Luogu P3300 [SDOI2013]城市规划」

标签:ddl   题目   ==   左右   clu   连通块   amp   维护   程序   

原文地址:https://www.cnblogs.com/Sxy_Limit/p/12965512.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!