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;
}

码风较奇葩(压行强迫症+卡常小能手

欢迎觉得做法很麻烦或有错误的巨佬来喷

posted @ 2021-09-08 17:39  Noir'  阅读(117)  评论(0)    收藏  举报