「Luogu P3300 [SDOI2013]城市规划」
题目大意
给出一个 \(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;
}