P1457 [USACO2.1]城堡 The Castle 题解
这道题似乎还没有最短路的题解,这里给出一种\(dijkstra\)的做法(其实是最近学图论学疯了,什么都想用图论来做)
题目大意:
给出一个图,查询这个图的:
连通块的个数、
最大连通块的大小、
合并相邻连通块后最大的连通块(以及这个可以调死你的拆墙的方向)
分析:
首先看到数据范围: \(1≤n,m≤50\)
我们给每个点一个编号,让其与相邻4个点连边;(注意这里是连单向边,因为题目给出的每个点都描述了自己周围墙的状态,所以只用连自己的那条就行了)
建图代码:
int pos[N];
int cnt,head[N];
struct edge{int to,nxt;}e[N<<1];
inline void addedge(int x,int i,int j){
if(!(x&1)) add(m*(i-1)+j,m*(i-1)+j-1);//西
if(!(x&(1<<1))) add(m*(i-1)+j,m*(i-2)+j);//北
if(!(x&(1<<2))) add(m*(i-1)+j,m*(i-1)+j+1);//东
if(!(x&(1<<3))) add(m*(i-1)+j,m*i+j);//南
}
main(void){
m=read();n=read();num=n*m;
for(int tmp,i=1;i<=n;i++)
for(int j=1;j<=m;j++){
tmp=m*(i-1)+j;
addedge((pos[tmp]=read()),i,j);
}
}
建完图后考虑怎么统计连通块的数量
遍历所有点,如果该点没有被访问过,那么就可以新开一个连通块,数量++;
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=num;i++)
if(!vis[i]) ++room,siz[++tot]=dijkstra(i);
至于连通块的大小,可以在\(dijkstra\)里面统计:
int dis[N][N]
bool vis[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
inline int dijkstra(int s){
int sum=0;//用来统计当前连通块的大小
dis[s][s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
int u=q.top().second;
q.pop();
if(!vis[u]){
vis[u]=1;
belong[u]=tot;++sum;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[s][v]>dis[s][u]+1){
dis[v][s]=dis[s][v]=dis[s][u]+1;
if(!vis[v]) q.push(make_pair(dis[s][v],v));
}
}
}
}
return sum;//dijkstra的类型设为int
}
那么刚刚的遍历也要有改变,用\(maxn\)记录最大值:
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=num;i++)
if(!vis[i]) ++room,maxn=max(maxn,(siz[++tot]=dijkstra(i)));
前两个问题解决后,就要来思考怎么计算加了一条边后最大连通块大小
- 第一种想法:重新再跑一遍\(dijkstra\)
但是复杂度会很大(会不会炸我没有试,但是它不够优,所以舍)
- 那么就有了第二种想法:将原本的图转变为若干块(
缩点??)
\(O(nm)\)遍历所有点,如果它上边或右边的点和它不属于同一个块,那么更新\(ans\)为
\(max(ans,siz[\)当前块\(]+siz[\)另一个块\(])\)
然后记录当前推到墙的位置和方向
但是由于题目没有spj要求很复杂:
选择最佳的墙来推倒。有多解时选最靠西的,仍然有多解时选最靠南的。同一格子北边的墙比东边的墙更优先。
用该墙的南邻单位的北墙或西邻单位的东墙来表示这面墙,方法是输出邻近单位的行数、列数和墙的方位(
N(北)或者E(东))。
所以当 \(ans==siz[\)当前块\(]+siz[\)另一个块\(]\) 时,我们还需要特判方向的优劣性
具体代码:
#include<bits/stdc++.h>
#define N 2505
#define inf 0x3f3f3f3f
#define endl '\n'
using namespace std;
int n,m,num;
int room,maxn=-inf,ans=-inf;
int wall[4];
char dir;
int dis[N][N],pos[N];
int belong[N],siz[N],tot;
bool vis[N];
int cnt,head[N];
struct edge{int to,nxt;}e[N<<1];
priority_queue<pair<int,int>,vector<pair<int,int> greater<pair<int,int> > >q;//堆优化
inline void add(register int u,register int v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline int read(){
register int f=1,k=0;
register char c=getchar();
while(c!='-'&&(c<'0'||c>'9')) c=getchar();
if(c=='-') f=-1,c=getchar();
while(c>='0'&&c<='9') k=(k<<3)+(k<<1)+(c^48),c=getchar();
return f*k;
}
inline void write(register int x){
if(x<0) x=-x, putchar('-');
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline int dijkstra(int s){
int sum=0;//统计当前连通块的大小
dis[s][s]=0;
q.push(make_pair(0,s));//堆优化
while(!q.empty()){
int u=q.top().second;
q.pop();
if(!vis[u]){
vis[u]=1;
belong[u]=tot;//将当前点加入连通块
++sum;//连通块大小+1
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[s][v]>dis[s][u]+1){
dis[v][s]=dis[s][v]=dis[s][u]+1;
if(!vis[v]) q.push(make_pair(dis[s][v],v));
}
}
}
}
return sum;
}
inline void addedge(int x,int i,int j){
//题目中的1,2,4,8其实就是让你用二进制来判断
if(!(x&1)) add(m*(i-1)+j,m*(i-1)+j-1);//西
if(!(x&(1<<1))) add(m*(i-1)+j,m*(i-2)+j);//北
if(!(x&(1<<2))) add(m*(i-1)+j,m*(i-1)+j+1);//东
if(!(x&(1<<3))) add(m*(i-1)+j,m*i+j);//南
}
main(void){
m=read();n=read();num=n*m;//num记录总点数
for(int tmp,i=1;i<=n;i++)
for(int j=1;j<=m;j++){
tmp=m*(i-1)+j;//当前点的编号
addedge((pos[tmp]=read()),i,j);//加边
}
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=num;i++)
if(!vis[i]) ++room,maxn=max(maxn,(siz[++tot]=dijkstra(i)));
write(room),putchar('\n');//房间数
write(maxn),putchar('\n');//最大房间
for(int tmp,i=n;i>=1;i--)//从下到上
for(int j=1;j<=m;j++){//从左到右
tmp=m*(i-1)+j;//编号
if(i^1&&pos[tmp]&(1<<1))//如果北边不是地图边界且有墙可拆
if(belong[tmp]^belong[tmp-m]){//并且这面墙隔着的两个点不属于同一个连通块
if(ans==siz[belong[tmp]]+siz[belong[tmp-m]])
if(j<wall[2]) wall[1]=i,wall[2]=j,dir='N';//特判:优先选西南房间的墙来拆
if(ans<siz[belong[tmp]]+siz[belong[tmp-m]]){
ans=siz[belong[tmp]]+siz[belong[tmp-m]];
wall[1]=i;wall[2]=j;dir='N';//记录答案
}
}
if(j^m&&pos[tmp]&(1<<2))//如果东边不是地图边界且有墙可拆
if(belong[tmp]^belong[tmp+1]){//并且这面墙隔着的两个点不属于同一个连通块
if(ans==siz[belong[tmp]]+siz[belong[tmp+1]])
if(j<wall[2]) wall[1]=i,wall[2]=j,dir='E';//特判:优先选西南房间的墙来拆
if(ans<siz[belong[tmp]]+siz[belong[tmp+1]]){
ans=siz[belong[tmp]]+siz[belong[tmp+1]];
wall[1]=i;wall[2]=j;dir='E';//记录答案
}
}
}
write(ans),putchar('\n');//最大合并连通块
write(wall[1]),putchar(' ');//第几行
write(wall[2]),putchar(' ');//第几列
putchar(dir),putchar('\n');//方向
return 0;
}
码风较奇葩(压行强迫症+卡常小能手)
欢迎觉得做法很麻烦或有错误的巨佬来喷

浙公网安备 33010602011771号