城市规划 解题报告

题目描述

分析题意

首先我们需要理解 “联通” 这个概念。有两种情况被称为“联通”:

  • 对于同一列的相邻的两个格子,它们都是 “.”,“|”或“O”三者之一
  • 对于同一行的相邻的两个格子,它们都是 “.”,“-”或“O”三者之一

然后我们看到本题需要维护联通块,因此可以考虑使用并查集维护联通块。
其次,让我们关注询问区间 \((l,1)(r,M)\),这等价于询问第 \(l\) 行到第 \(r\) 行,因此我们可以考虑到把每一行看做一个整体进行处理。
最后,让我们看到数据范围:\(N \leq 1e5\)\(M \leq 6\)。是熟悉的 \(1e5\),再结合区间查询,让我们想到线段树

算法分析

在思考怎么使用线段树时,我们会遇到一个问题:怎么合并子节点信息?
结合我们之前学过的“用线段树求最大子段和”,我们可以想到我们只需要处理两个儿子相邻的两行就可以了。
对于这两行(第 \(i\) 行和第 \(i+1\) 行),如果 \((i,j)\)\((i+1,j)\) 是联通的,那么我们就要用并查集合并这两个联通块,同时维护权值(如果被合并的两个联通块都有建筑,那么合并后总权值应该减一,因为这两个联通块在各自节点都被统计过)。
这里有一个问题:在合并后,由部分并查集的根节点并不在最上和最小这两行。我们可以将并查集的根节点转移到这两行上的点来解决问题(详见代码)。
其余的查询和修改与正常的线段树一样,不再赘述。
“talk is cheap,show me the code”

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls (i<<1)
#define rs (i<<1|1)
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
inline int read(){
  register int x=0;
  char c=getchar();
  while(c<'0' || '9'<c) c=getchar();
  while('0'<=c && c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
  return x;
}
const int N=1e5+1000,M=8;
int n,m,q,p[N][M];
//连通性判断
bool able_x(int val){return val==0 || val==1 || val==3;}
bool able_y(int val){return val==0 || val==1 || val==2;}
struct Data{
  int l,r,f[M<<2],cnt;
  bool have[M<<2];
  int find(int x){return f[x]=(f[x]==x)?x:find(f[x]);}
  bool Union(int x,int y){//合并联通块,返回值为真当且仅当 合并成功且合并的两个联通块都有建筑
    int fx=find(x),fy=find(y);
    if(fx==fy) return false;
    bool res=(have[fx] && have[fy]);
    f[fx]=fy;
    have[fy]|=have[fx];
    return res;
  }
  Data(int x=0){
    l=r=x,cnt=0;
    for(int i=1;i<=2*m;i++) f[i]=i,have[i]=0;
    for(int i=1;i<=m;i++)
      have[i]=(!p[x][i]),Union(i+m,i);
    for(int i=1;i<m;i++)
      if(able_x(p[x][i]) && able_x(p[x][i+1]))
        Union(i,i+1);
    for(int i=1;i<=2*m;i++)
      if(f[i]==i && have[i])
        cnt++;
  }
};
int rk[M<<2],nwf[M<<2],nwhave[M<<2];
Data Merge(Data a,Data b){
  Data ans;
  ans.cnt=a.cnt+b.cnt;
  ans.l=a.l,ans.r=b.r;
  for(int i=1;i<=m;i++){//继承两个儿子的所有并查集 和 每个并查集是否有建筑
    //[1,m] 左儿子左侧 [m+1,2m] 右儿子右侧 [2m+1,3m] 左儿子右侧 [3m+1,4m] 右儿子左侧
    ans.f[i]     = a.f[i]  <=m ? a.f[i]       : a.f[i]+m;
    ans.f[i+m]   = b.f[i+m]<=m ? b.f[i+m]+3*m : b.f[i+m];
    ans.f[i+2*m] = a.f[i+m]<=m ? a.f[i+m]     : a.f[i+m]+m;
    ans.f[i+3*m] = b.f[i]  <=m ? b.f[i]+3*m   : b.f[i];
    ans.have[i]     = a.have[i];
    ans.have[i+m]   = b.have[i+m];
    ans.have[i+2*m] = a.have[i+m];
    ans.have[i+3*m] = b.have[i];
  }
  for(int i=1;i<=m;i++)
    if(able_y(p[a.r][i]) && able_y(p[b.l][i]))
      ans.cnt+= ans.Union(i+2*m,i+3*m) ? -1 : 0;
  memset(rk,0,sizeof(rk));
  for(int i=1,u;i<=2*m;i++)//找到每一个并查集的根节点,并准备将根节点转移到 在该并查集的 且位于两侧的任意一个节点(rk[u])
    u=ans.find(i),rk[u]=i;
  for(int i=1;i<=2*m;i++)//这个地方不能直接修改并查集!!! 因为我们仍需要利用旧的并查集去查找原根节点
    nwf[i]=rk[ans.find(i)],nwhave[i]=0;
  for(int i=1;i<=4*m;i++)//这个地方就可以直接修改,因为之后的操作就和原并查集无关了
    if(rk[i])
      ans.have[rk[i]]=ans.have[i];
  for(int i=1;i<=2*m;i++)
    ans.f[i]=nwf[i];
  return ans;
}
struct Tree{int l,r;Data val;}t[N<<2];
void push_up(int i){t[i].val=Merge(t[ls].val,t[rs].val);}
void build(int i,int l,int r){
  t[i].l=l,t[i].r=r;
  if(l==r){
    t[i].val=Data(l);
    return;
  }
  int mid=l+r>>1;
  build(ls,l,mid);
  build(rs,mid+1,r);
  push_up(i);
}
Data query(int i,int l,int r){
  if(l<=t[i].l && t[i].r<=r)
    return t[i].val;
  int mid=t[i].l+t[i].r>>1;
  if(l>mid) return query(rs,l,r);
  if(mid>=r) return query(ls,l,r);
  return Merge(query(ls,l,r),query(rs,l,r));
}
void update(int i,int x){
  if(t[i].l==t[i].r){
    t[i].val=Data(x);
    return;
  }
  int mid=t[i].l+t[i].r>>1;
  if(x<=mid) update(ls,x);
  else update(rs,x);
  push_up(i);
}
char s[10],c;
int main(){
  n=read(),m=read();
  memset(p,inf,sizeof(p));
  for(int i=1;i<=n;i++){
    scanf("%s",s+1);
    for(int j=1;j<=m;j++){
      c=s[j];
      if(c=='O') p[i][j]=0;
      if(c=='+') p[i][j]=1;
      if(c=='|') p[i][j]=2;
      if(c=='-') p[i][j]=3;
      if(c=='.') p[i][j]=4;
    }
  }
  build(1,1,n);
  q=read();
  char opt[3];
  for(int i=1,l,r;i<=q;i++){
    scanf("%s",opt),l=read(),r=read();
    if(opt[0]=='Q')
      printf("%d\n",query(1,l,r).cnt);
    else{
      scanf("%s",opt);
      if(opt[0]=='O') p[l][r]=0;
      if(opt[0]=='+') p[l][r]=1;
      if(opt[0]=='|') p[l][r]=2;
      if(opt[0]=='-') p[l][r]=3;
      if(opt[0]=='.') p[l][r]=4;
      update(1,l);
	  }
  }
  return 0;
}
posted @ 2025-07-07 18:04  XiaoZi_qwq  阅读(9)  评论(0)    收藏  举报