P1457 [USACO2.1]城堡 The Castle(bfs)

题目背景

我们憨厚的 USACO 主人公农夫约翰(Farmer John)以无法想象的运气,在他生日那天收到了一份特别的礼物:一张“幸运爱尔兰”(一种彩票)。结果这张彩票让他获得了这次比赛唯一的奖品——坐落于爱尔兰郊外的一座梦幻般的城堡!

题目描述

喜欢吹嘘的农夫约翰立刻回到有着吹嘘传统的威斯康辛老家开始吹嘘了, 农夫约翰想要告诉他的奶牛们关于他城堡的一切。他需要做一些吹嘘前的准备工作:比如说知道城堡有多少个房间,每个房间有多大。

另外,农夫约翰想要把一面单独的墙(指两个单位间的墙)拆掉以形成一个更大的房间。 你的工作就是帮农夫约翰做以上的准备,算出房间数与房间的大小。

城堡的平面图被划分成 n \times mn×m 个正方形的单位,一个这样的单位可以有 0 \sim 40∼4 面墙环绕。城堡周围一定有外墙环绕以遮风挡雨。(就是说平面图的四周一定是墙。)

请仔细研究下面这个有注解的城堡平面图:

友情提示,这个城堡的平面图是 4 \times 74×7 个单位的。一个“房间”的是平面图中一个由 #-| 围成的格子(就是图里面的那一个个的格子)。比如说这个样例就有 55 个房间。(大小分别为 9,7,3,1,89,7,3,1,8 个单位(排名不分先后))

移去箭头所指的那面墙,可以使 22 个房间合为一个新房间,且比移去其他墙所形成的房间都大。

城堡保证至少有 22 个房间,而且一定有一面墙可以被移走。

输入格式

第一行两个正整数 m,nm,n,表示城堡有 nn 行 mm 列。

每一个单位的数字告诉我们这个单位的东西南北是否有墙存在。每个数字是由以下四个整数中的任意个加起来的。

11: 在西面有墙

22: 在北面有墙

44: 在东面有墙

88: 在南面有墙

城堡内部的墙会被规定两次。比如说 (1,1)(1,1) 南面的墙,亦会被标记为 (2,1)(2,1) 北面的墙。

输出格式

输出包含如下四行:

第一行:城堡的房间数目。

第二行:最大的房间的大小

第三行:移除一面墙能得到的最大的房间的大小

第四行:移除哪面墙可以得到面积最大的新房间。

选择最佳的墙来推倒。有多解时选最靠西的,仍然有多解时选最靠南的。同一格子北边的墙比东边的墙更优先。

用该墙的南邻单位的北墙或西邻单位的东墙来表示这面墙,方法是输出邻近单位的行数、列数和墙的方位( N(北)或者 E(东))。

输入输出样例

输入 #1复制

7 4
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13

输出 #1复制

5
9
16
4 1 E

说明/提示

【数据范围】
对于 100\%100% 的数据,1\le n,m \le 501≤n,m≤50。

USACO 2.1

翻译来自NOCOW

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll Inf=0x3f3f3f3f3f3f3f3f;
int n,m;
int a[55][55][4];  //记录四个方向是否有墙
int order[55][55];  //记录这个点属于那个连通块
int s;             //连通块的编号
int f[2899];       //f[i]表示编号为i的连通块里有多少元素
bool vis[55][55]; //bfs标记这个点是否被访问过
int maxn;          //记录最大连通块元素数量
int sum;            //连通块个数
int num;            //某狗连通块内元素个数
int res;            //拆掉一堵墙后最大连通块元素数量
int ans1,ans2;       //拆掉的墙所属位置
char op;           //E还是N墙
struct node{  
    int x;
    int y;
};
queue<node>q;
void bfs(){  //一次bfs找一个连通块
    s++;
    int nxt[4][2]={{0,-1},{-1,0},{0,1},{1,0}};
    int i;
    num=0;
    while(!q.empty()){
        node cur=q.front();
        q.pop();
        num++;                        //本连通块元素数量
        order[cur.x][cur.y]=s;      //每次bfs把这次bfs所访问到的的点打上所属连通块的标记
        for(i=0;i<4;i++){
            int tx=cur.x+nxt[i][0];
            int ty=cur.y+nxt[i][1];
            if(tx<1||ty<1||tx>n||ty>m)continue;
            if(!vis[tx][ty]&&a[cur.x][cur.y][i]==0){  
                vis[tx][ty]=1;
                q.push(node{tx,ty});
            }
        }
    }
    f[s]=num; //把此次bfs找到的连通块的元素数量记为num,所有属于这个连通块的元素都可以用
    maxn=max(maxn,num);
    return ;
}
int main (){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>m>>n;
    int i,j,k;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            int x;
            cin>>x;
            if((x&1)==1)a[i][j][0]=1;//位运算 1 2 4 8都是2的次幂,二进制分别0001 0010 0100 1000
            if((x&2)==2)a[i][j][1]=1;//注意位运算的优先级,==优先级大于&
            if((x&4)==4)a[i][j][2]=1;
            if((x&8)==8)a[i][j][3]=1;
        }
    }
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            if(!vis[i][j]){   //bfs找连通块
                q.push(node{i,j});
                vis[i][j]=1;
                sum++;
                bfs();
            }
        }
    }
    //cout<<sum<<" "<<maxn<<endl;
    for(j=1;j<=m;j++){       //从西到东,从南到北
        for(i=n;i>=1;i--){   //北墙优先,东墙次之
            for(k=1;k<=2;k++){
                if(k==1){
                    if(i==1)continue;
                    if(a[i][j][1]==1&&order[i][j]!=order[i-1][j]){
                        //注意,条件是两元素间有墙且属于不同连通块
                        int tmp=f[order[i][j]]+f[order[i-1][j]];
                        if(tmp>res){
                            res=tmp;
                            ans1=i;
                            ans2=j;
                            op='N';
                        }
                    }
                }else if(k==2){
                    if(j==m)continue;
                    if(a[i][j][2]==1&&order[i][j]!=order[i][j+1]){
                        int tmp=f[order[i][j]]+f[order[i][j+1]];
                        if(tmp>res){
                            res=tmp;
                            ans1=i;
                            ans2=j;
                            op='E';
                        }
                    }
                }
            }
        }
    }
    cout<<sum<<endl;
    cout<<maxn<<endl;   
    cout<<res<<endl;
    cout<<ans1<<" "<<ans2<<" "<<op<<endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值