ZJNU 2414 - Zoo (bfs)

COCI 2019/2020 CONTEST #1 - Zoo

ZJNU 2414 - Zoo


题意

动物园中有不限数量的两种动物\(T\)\(B\)

动物园可以看成是\(n\times m\)的一张图,每只动物都会从起点\((1,1)\)开始,在终点\((n,m)\)结束,过程中可能会随机走动

每种动物会在它走过的路径上留下脚印

后一只动物如果走过前一只动物的路径,则后一只动物的脚印会覆盖前一只的脚印

给定一张走完后的图,问这张图表示至少有多少只动物从起点走到终点

pic


限制

\(2\leq n,m\leq 1000\)




思路

由于每只动物的脚印会覆盖前一只动物的脚印

并且每只动物都会从\((1,1)\)走到\((n,m)\)

那么也就表示每只动物走完后,\((1,1)\)\((n,m)\)一定在同一连通块中

由于求的是至少有多少只动物

所以可以看成这个连通块代表最后一只动物走过的地点,并且两种动物交替进行

因此对于这张图,可以从最后一只动物开始倒过来推导


每次\((1,1)\)\((n,m)\)所属连通块都能看成最后一只动物走过的地点

且对于所有动物,这两个点他们一定会经过

所以可以假设后一只动物所走过的地点,前一只动物必定也走过

换言之,除了第一只动物外,后一只动物所走过的点一定是前一只动物走过的点的一部分

那么做法就很明显了,每次从\((1,1)\)搜索相同种类脚印的连通块,并且全部将其翻转成另外一种动物的脚印(\(B\)\(T\)\(T\)\(B\)

一直这样循环搜索下去,直到某一次搜索的连通块包含了全图的所有脚印(或者说全图只剩下一种脚印)

那么搜索的次数就表示至少的动物数量,见下方程序一

明显对于上述做法,每次会对当前搜索到的连通块进行翻转,假如所能判断出的动物数量很多,那么翻转的次数将会大大增加(最高时间复杂度大概为\(O(n^4)\)


而对于上面的想法,如果不进行脚印的翻转

可以发现,实际上可以对图进行分层处理

假设刚开始的\((1,1)\)点所在连通块深度为\(1\),那么以独立的相同种类脚印的连通块为一层,通过搜索让整幅图进行分层

那么答案就是脚印深度的最大值

  • 举例,假设当前脚印如下图所示

ex1

  • \((1,1)\)所在连通块定为深度\(1\)

ex2

  • 将所有脚印借助连通块来进行分层,定深度

ex3

  • 在本张图中发现最深的脚印深度为\(4\),故至少会有\(4\)只动物

所以直接搜索进行深度计算即可,见下方程序二

如程序二中的写法,借助\(bfs\)来每次判断相邻格子脚印的种类,并根据种类来分类将深度取小

如果种类相同,则深度与“自身”取小

如果种类不同,则深度与“自身+1”取小

明显的,这种方法也可以被一种“环形迷宫”形的样例给卡住,主要是搜索连通块时每次都会根据“是否能够取小”来决定是否进入队列,对于某些点可能会重复进入队列,导致时间复杂度剧增

时间复杂度定高于\(O(n^3)\),接近\(O(n^4)\)


鉴于“可能会重复进入队列”这一点,可以对其专门进行优化

由于我们需要对图进行分层,就能够保证相邻连通块深度差值必为\(1\)

所以每次我们只需要对相同深度的节点进行一次搜索

定义两个队列,交替使用

  • 如果搜索到的某个点种类与当前不一致且“能够取小”,则代表它的深度为当前搜索的连通块深度+1,则放入另外一个队列中

  • 如果搜索到的某个点种类与当前不一致且“不能够取小”,则代表它的深度为当前搜索的连通块深度+1,但已经放入另一个队列中了,无需重复放入

  • 如果搜索到的某个点种类与当前一致且“能够取小”,则代表与当前点属于同一连通块中,放入当前队列

  • 如果搜索到的某个点种类与当前一致且“不能够取小”,则代表与当前点属于同一连通块中,但当前这一遍搜索已经搜索过这个点了,无需重复搜索

根据这四条规则(实际上只要写两条就行),可以将时间复杂度严格控制在\(O(n^2)\)

搜索到两个队列均没有剩余内容为止




程序一(Time Limit Exceed - 70')

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;

int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n,m;
char mp[1050][1050];

inline bool prim(int x,int y)
{
    return x>0&&y>0&&x<=n&&y<=m;
}

int main()
{
    bool flag=false;
    int cnt=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",mp[i]+1);
        for(int j=1;j<=m;j++)
            if(mp[i][j]!='*')
                flag=true,cnt++;
    }
    if(!flag)
    {
        puts("0");
        return 0;
    }
    
    int ans=0;
    while(true)
    {
        ans++; //记录搜索次数
        queue<P> q;
        q.push(P(1,1));
        char c=mp[1][1];
        mp[1][1]=(mp[1][1]=='B'?'T':'B'); //翻转(1,1)
        int cntd=1;
        while(!q.empty())
        {
            P pd=q.front();
            q.pop();
            for(int i=0;i<4;i++)
            {
                int px=pd.first+dx[i],py=pd.second+dy[i];
                if(prim(px,py)&&mp[px][py]==c) //相同种类连通块
                {
                    mp[px][py]=mp[1][1]; //连通块内全部翻转
                    cntd++;
                    q.push(P(px,py));
                }
            }
        }
        if(cnt==cntd) //只剩一个连通块时
            break;
    }
    printf("%d\n",ans);
    
    return 0;
}



程序二(Time Limit Exceed - 76')

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;

int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n,m,dep[1050][1050];
char mp[1050][1050];

inline bool prim(int x,int y)
{
    return x>0&&y>0&&x<=n&&y<=m;
}

int main()
{

    bool flag=false;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",mp[i]+1);
        for(int j=1;j<=m;j++)
        {
            dep[i][j]=0x3f3f3f3f;
            if(mp[i][j]!='*')
                flag=true;
        }
    }
    dep[1][1]=1; //起始点深度定作1
    
    if(!flag)
    {
        puts("0");
        return 0;
    }
    
    queue<P> q;
    q.push(P(1,1));
    while(!q.empty())
    {
        P pd=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int px=pd.first+dx[i],py=pd.second+dy[i];
            if(prim(px,py)&&mp[px][py]!='*')
            {
                if(mp[px][py]==mp[pd.first][pd.second]&&dep[px][py]>dep[pd.first][pd.second])
                { //如果脚印相同,与深度取小
                    dep[px][py]=dep[pd.first][pd.second];
                    q.push(P(px,py));
                }
                else if(mp[px][py]!=mp[pd.first][pd.second]&&dep[px][py]>dep[pd.first][pd.second]+1)
                { //如果脚印不同,与深度+1取小
                    dep[px][py]=dep[pd.first][pd.second]+1;
                    q.push(P(px,py));
                }
            }
        }
    }
    
    int ans=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j]!='*')
                ans=max(ans,dep[i][j]); //找出最大深度作为答案
    printf("%d\n",ans);
    
    return 0;
}



程序三(Accepted)

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;

int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n,m,dep[1050][1050];
char mp[1050][1050];

inline bool prim(int x,int y)
{
    return x>0&&y>0&&x<=n&&y<=m;
}

int main()
{
    bool flag=false;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",mp[i]+1);
        for(int j=1;j<=m;j++)
        {
            dep[i][j]=0x3f3f3f3f;
            if(mp[i][j]!='*')
                flag=true;
        }
    }
    dep[1][1]=1;

    if(!flag)
    {
        puts("0");
        return 0;
    }

    queue<P> q[2];
    q[0].push(P(1,1));
    int cur=0;
    while(!q[cur].empty()) //假如另一队列非空,则将另一队列作为当前队列
    {
        while(!q[cur].empty()) //假如当前队列非空
        {
            P pd=q[cur].front();
            q[cur].pop();
            for(int i=0;i<4;i++)
            {
                int px=pd.first+dx[i],py=pd.second+dy[i];
                if(prim(px,py)&&mp[px][py]!='*')
                {
                    if(mp[px][py]==mp[pd.first][pd.second]&&dep[px][py]>dep[pd.first][pd.second])
                    { //同类且能取小
                        dep[px][py]=dep[pd.first][pd.second];
                        q[cur].push(P(px,py)); //放入当前队列
                    }
                    else if(mp[px][py]!=mp[pd.first][pd.second]&&dep[px][py]>dep[pd.first][pd.second]+1)
                    { //不同类且能取小
                        dep[px][py]=dep[pd.first][pd.second]+1;
                        q[cur^1].push(P(px,py)); //放入另一个队列
                    }
                }
            }
        }
        cur^=1; //队列下标切换
    }

    int ans=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j]!='*')
                ans=max(ans,dep[i][j]); //取大输出
    printf("%d\n",ans);
    
    return 0;
}

posted @ 2020-09-15 00:00  StelaYuri  阅读(184)  评论(0编辑  收藏  举报