• ZJNU 2414


    COCI 2019/2020 CONTEST #1 - Zoo

    ZJNU 2414 - Zoo


    题意

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

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

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

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

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

    pic


    限制

    (2leq n,mleq 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
    ",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
    ",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
    ",ans);
        
        return 0;
    }
    

  • 相关阅读:
    拖拽组件
    css3动画 巧用label
    轮播图
    弹出框组件,可拖拽
    基于angularJS的分页功能
    身份证验证大全-javascript
    公用拖拽功能插件
    记录那些年我踩过的坑
    节流函数
    手机号码的正则表达式
  • 原文地址:https://www.cnblogs.com/stelayuri/p/13670275.html
Copyright © 2020-2023  润新知