「Luogu P3820 小D的地下温泉」
这道题的考点比较多.
前置芝士
- BFS(DFS),这两种算法在这道题中并没有什么特别突出的地方,基本就是自己看心情写(本文以DFS为准,
所以我心情是好是坏呢?) - 连通块,可以将每一个温泉看作一个连通块,这样就变成了一个图上问题.
- 并查集,在判断图中元素是否相连时需要用到(具体下文会讲到).
具体做法
先用搜索将每个温泉(连通块),处理出来,并且统计出每个连通块的大小.在每次查询时只需要查询当前连通块的大小,取出最大的连通块并输出编号即可.那么,问题就在修改操作了.
当修改的点是水时只需要将当前连通块的数量-1,并且将这个位置改为土地就好了,但是当这个位置是土地时可能会将两块本不是相连的连通块连在一起,数据还是很大的,如果暴力修改肯定是会T的,这时,并查集派上用场了.当两个块因为这个点变成了温泉而相连时可以将其中一个连通块的father指向另一个连通块的father,将sum也相加这个问题就解决了,当这个位置并,没有将不同连通块连在一起时需要再开一个连通块.
细节
- N,M很大,不能用二维数据储存,但是NM并不大,所以对于(x,y)可以将它储存在map[(x-1)*M+y]中,但是这样储存时对于边界一定要特别判断,不然很容易就会出错.
- 并查集初始化时father[i]=i建议i从1到N*M赋值,或者在开出一个新的连通块时赋值.
- 在修改过程中可能会将同一块连通块相连,需要特别判断,勤用Find().
代码
#include<bits/stdc++.h>
#define rap(i,first,last) for(int i=first;i<=last;++i)
#define ID ((x-1)*M+y)//一时define一时爽,一直define一直爽
using namespace std;
const int maxNM=1e6+7;//N*M的最大值
const int move_x[5]={233,1,-1,0,0};//向四个方向走时用的常量数组
const int move_y[5]={233,0,0,1,-1};
int N,M;
int now=0;
int num[maxNM];//记录每个点所在的连通块的编号
int sum[maxNM];//每个连通块的大小
int _map[maxNM];//每个点的状态(是温泉还是土)
bool visit[maxNM];//在搜索时判断这个点是否到过
int father[maxNM];//记录每个点的father
int X[maxNM],Y[maxNM];
void DFS(int x,int y)//DFS遍历全图(BFS同理)
{
if(x<1||x>N||y<1||y>M)return;
if(!_map[ID])return;
if(visit[ID])return;
visit[ID]=1;
num[ID]=now;
sum[now]++;
rap(i,1,4)
DFS(x+move_x[i],y+move_y[i]);
}
int Find(int now)//并查集时用的Find函数(带压缩路径)
{
if(father[now]==now)return now;
return father[now]=Find(father[now]);
}
int cnt=0;//这个点变为温泉后会影响到的温泉数
int to[6];//影响到的温泉的编号
void Add(int nx,int ny)
{
int x=nx,y=ny;//define实在是好用
_map[ID]=1;
cnt=0;
rap(i,1,4)
{
x=nx+move_x[i];
y=ny+move_y[i];
if(x>0&&x<=N&&y>0&&y<=M)//判断边界
{
if(_map[ID])
to[++cnt]=Find(num[ID]);
}
}
x=nx;
y=ny;
if(cnt==0)//如果没有影响到其它温泉就新开一个连通块
{
sum[++now]=1;
num[ID]=now;
return;
}
sum[Find(to[1])]++;//将这个点放入其中一个连通块
num[ID]=to[1];
rap(i,2,cnt)
if(Find(to[i])!=Find(to[1]))//注意判断连通性
{
sum[to[1]]+=sum[to[i]];//连通块合并
father[to[i]]=father[to[1]];
}
}
int main()
{
scanf("%d%d",&N,&M);
char ch;
rap(x,1,N)
rap(y,1,M)
{
cin>>ch;
if(ch=='.')_map[ID]=1;
}
rap(i,1,N*M)father[i]=i;//懒得再其它地方再写
rap(x,1,N)//遍历全图,找连通块
rap(y,1,M)
if(!visit[ID]&&_map[ID])
{
now++;
DFS(x,y);
}
int Q,w,top,c,x,y;
scanf("%d",&Q);
rap(i,1,Q)
{
scanf("%d%d",&c,&w);
if(c==1)//查询
{
top=0;
rap(j,1,w)
{
scanf("%d%d",&X[j],&Y[j]);
x=X[j];
y=Y[j];
top=max(top,sum[Find(num[ID])]);//找到最大的连通块的大小
}
rap(j,1,w)
{
x=X[j];
y=Y[j];
if(sum[Find(num[ID])]==top)//输出第一个最大的连通块的编号
{
printf("%d\n",j);
break;
}
}
}
if(c==2)
{
rap(j,1,w)
{
scanf("%d%d",&X[i],&Y[i]);
x=X[i];
y=Y[i];
if(_map[ID]==0)//如果是土地就用Add
Add(X[i],Y[i]);
else//不是土地直接减去就行
{
sum[Find(num[ID])]--;
_map[ID]=0;
num[ID]=0;
}
}
}
}
}