zoukankan      html  css  js  c++  java
  • 「Luogu P3300 [SDOI2013]城市规划」

    题目大意

    给出一个 (n imes 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
    ",Query(left,right).sum);
    		}
    		if(opt=='C')
    		{
    			scanf("%d%d",&x,&y);
    			cin>>ch;
    			arr[x][y]=ch;
    			Updata(x);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    字符串Hash 学习笔记
    P4315 月下“毛景树” 题解
    page
    Equation
    Graph
    配置UOJ数据的正确姿势
    luogu2261余数求和题解--整除分块
    luogu2858奶牛零食题解--区间DP
    luogu1005矩阵取数游戏题解--区间DP
    luogu4677山区建小学题解--区间DP
  • 原文地址:https://www.cnblogs.com/Sxy_Limit/p/12965512.html
Copyright © 2011-2022 走看看