• P3958 奶酪


    原题链接  https://www.luogu.org/problemnew/show/P3958

    话说 j 让我从提高组的题里面选一个讲,翻了好久,终于找到了这道 水 好题QwQ~

    先来看题目:

    大体看一遍题,觉得这个题应该是搜索吧~

    点开算法标签看看呗:

    哇,搜索蒙对了,不过……我觉得是深度优先搜索 dfs。

    其实深度优先应该是最快的。

    首先,我们找出所有可以从下表面进入的球,然后深度优先搜一遍。一旦遇到一个点最高处高度 z + r >= h,就表示可以到上表面,退出。因为每个洞最多访问一次(只需要求是否能到达上表面,而不是最短步数),然后决定下一步的时候还需要O(n)的时间。所以总时间复杂度是O(n*t)

    我们看上面这个图再理解一下:

    先在所有的空洞中找出联通底部的空洞(符合条件 z - r <= 0),然后沿着这个空洞往上 dfs ,直到所到达的空洞已经通向顶部(符合条件 z + r >= h),那么就不用再向上搜索了,否则就一直往上搜,直到遍历完所有的空洞。

    代码实现

    我们先设一个结构体来表示每个空洞球心的坐标:

    struct dole
    {
        long long x,y,z;     //分别表示球心的横坐标x,纵坐标y,竖坐标z
                             //对于本题只需知道竖坐标z是指球心在奶酪中的高度即可 
    }a[1001]; 

    一. dfs 的起点

    根据上面的思路,我们 dfs 的起点应该就是连接奶酪底部的那几个空洞。

    我们枚举每一个空洞,判断它是否通向底部即可。

            for(int j=1;j<=n;j++)         //枚举每个空洞 
            {
                   if(a[j].z-r<=0)        //判断通向底部的条件,感性理解一下 
                {
                    vis[j]=1;
                    dfs(j);               //从此空洞开始dfs 
                }
            }

    欢迎来到我也不知道会不会TLE但就是要优化系列QwQ~

    我们考虑优化:

    优化一:由于本题求的是是否有解,而不是有几个解,所以当我们在之前已经搜到解得话,我们就不必再往下搜了,直接 break 即可;

    但是你会发现几乎每组数据的答案都是“No”,也就是说在绝大多数情况下你都要遍历每个空洞,这样的话你的第一个优化就没有什么意义了,所以我们要接着优化;

    优化二:回过头来看我们判断空洞是否通向底部的条件: if ( a[ j ] . z - r <=0 ),考虑到如果有 a [ j ] . z - r > 0 的话,那么对于更大的 z,那么也一定不会满足条件的;这就启发我们可以将所有空洞的 z 从小到大排个序,然后我们开始找,如果有一个空洞的 z 不满足条件,那么对于以后更大的 z 也一定不满足条件,这时我们直接 break 掉就好啦;

    这是加上两层优化后的代码:

            sort(a+1,a+1+n,cmp);        //按照每个空洞的高度从小到大排序 
            for(int j=1;j<=n;j++) 
            {
                   if(bj) break;           //如果已经找到了解,就不必再往下找了 
                   if(a[j].z-r<=0)         //判断是否通向底部的条件,感性理解一下 
                {
                    vis[j]=1;
                    dfs(j);             //从此空洞开始dfs 
                }
                else break;             //以后的空洞也一定不满足了,直接break掉 
            }

     二. dfs 的过程

    确定了起点后,我们只要找与之相连通的空洞往上钻,直到钻到顶部。

    question:怎么判断两个空洞是否连通?

    answer:如果两个球的半径之和 ≥ 两个球球心的距离,那么两圆相交(切)。

    证明(看图理解):

    1. 当两球相切时,显然球心距离等于两倍的半径:

    2. 当两球相离时,此时球心距离大于两倍的半径:

    3. 当两球相交时,此时球心距离小于两倍的半径:

    然后就可以贴 dfs 过程了:

    double dis(long long x,long long y,long long z,long long xx,long long yy,long long zz)   //求两球心的距离 
    {
        return sqrt((double)pow(x-xx,2)+(double)pow(y-yy,2)+(double)pow(z-zz,2));
    }
    
    void dfs(int k)          //当前搜到了第k个空洞 
    {
        if(a[k].z+r>=h)      //判断是否到达顶部的条件,再感性理解一下 
        {
            bj=1;            //标记有解 
            return ;          
        }
        for(int i=1;i<=n;i++)
        {
            if(bj) return;   //找到出口就不用再找了 
            if(dis(a[k].x,a[k].y,a[k].z,a[i].x,a[i].y,a[i].z)<=r*2.0&&!vis[i])     //判断两个空洞是否相连通 
            {
                vis[i]=1;    
                dfs(i);      //接着往下dfs 
            }
        }
    }

    三.答案输出

    如果 bj =1 说明我们已经搜到解了,输出 Yes;否则输出 No ;

    完整代码(辣么详细了应该都懂吧~):

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int read()                         //读入优化 
    {
        char ch=getchar();
        int a=0,x=1;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-') x=-x;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            a=(a<<3)+(a<<1)+(ch-'0');
            ch=getchar();
        }
        return a*x;
    }
    
    double dis(long long x,long long y,long long z,long long xx,long long yy,long long zz)   //求两球心的距离 
    {
        return sqrt((double)pow(x-xx,2)+(double)pow(y-yy,2)+(double)pow(z-zz,2));
    }
    
    long long t,n,h,r;       //t组数据,n个空洞,奶酪的高度为h,每个空洞的半径为r 
    long long x,y,z;         //每个空洞的球心的坐标是(x,y,z)
    bool vis[1001],bj;
    
    struct dole
    {
        long long x,y,z;     //分别表示球心的横坐标x,纵坐标y,竖坐标z
                             //对于本题只需知道竖坐标z是指球心在奶酪中的高度即可 
    }a[1001]; 
    
    bool cmp(dole x,dole y)
    {
        return x.z<y.z;      //从低到高排序 
    }
    void dfs(int k)          //当前搜到了第k个空洞 
    {
        if(a[k].z+r>=h)      //判断是否到达顶部的条件,再感性理解一下 
        {
            bj=1;            //标记有解 
            return ;          
        }
        for(int i=1;i<=n;i++)
        {
            if(bj) return;   //找到出口就不用再找了 
            if(dis(a[k].x,a[k].y,a[k].z,a[i].x,a[i].y,a[i].z)<=r*2.0&&!vis[i])     //判断两个空洞是否相连通 
            {
                vis[i]=1;    
                dfs(i);      //接着往下dfs 
            }
        }
    }
    int main()
    {
        t=read();
        for(int i=1;i<=t;i++)
        {
            memset(vis,0,sizeof(vis));  
            bj=0;
            n=read();h=read();r=read(); //n个空洞,奶酪高为h,每个空洞的半径是r 
            for(int j=1;j<=n;j++)
            {
                a[j].x=read();          //空洞球心的坐标 
                a[j].y=read();
                a[j].z=read(); 
            }
            sort(a+1,a+1+n,cmp);        //按照每个空洞的高度从小到大排序 
            for(int j=1;j<=n;j++) 
            {
                   if(bj) break;        //如果已经找到了解,就不必再往下找了 
                   if(a[j].z-r<=0)      //判断是否通向底部的条件,感性理解一下 
                {
                    vis[j]=1;
                    dfs(j);             //从此空洞开始dfs 
                }
                else break;             //以后的空洞也一定不满足了,直接break掉 
            }
            if(bj) printf("Yes
    ");
            else printf("No
    ");
        } 
        return 0;
    }

    不知道水的时间多不多QwQ~ 

    更强的算法——并查集

    这个算法的思路也是挺新颖,也挺好理解的QwQ~,下面就让我给大家讲讲思路吧:

    对于每个空洞,我们需要先找出哪些空洞与顶部相通,哪些空洞与底部相通(条件就是上面刚说的,不用再讲了吧),然后枚举每个空洞找出所有与之相通的空洞,弄成一个父亲(明显并查集思想);最后我们再枚举每个与顶部相通的空洞和与底部相通的空洞,看看它们是不是一个父亲就好啦!

    懂思路就好啦,不用再详细分析了,时间可能不大够了QwQ~

    直接看代码吧(怕泥萌看不懂我附上了详细的注释,其实是题解第一篇啦):

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int read()                         //读入优化 
    {
        char ch=getchar();
        int a=0,x=1;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-') x=-x;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            a=(a<<3)+(a<<1)+(ch-'0');
            ch=getchar();
        }
        return a*x;
    }
    
    int f[1001];//并查集
    
    int find(int x)
    {
        if (x!=f[x]) f[x]=find(f[x]);
        return f[x];
    }//查找+路径压缩
    
    double dis(long long x,long long y,long long z,long long xx,long long yy,long long zz)   //求两球心的距离 
    {
        return sqrt((double)pow(x-xx,2)+(double)pow(y-yy,2)+(double)pow(z-zz,2));
    }
    
    struct dole
    {
        long long x,y,z;     //分别表示球心的横坐标x,纵坐标y,竖坐标z
                             //对于本题只需知道竖坐标z是指球心在奶酪中的高度即可 
    }a[1001];
    
    bool bj; 
    long long t,n,h,r;       //t组数据,n个空洞,奶酪的高度为h,每个空洞的半径为r 
    long long x,y,z;         //每个空洞的球心的坐标是(x,y,z)
    int top_num,bottom_num;  //分别表示与奶酪顶部和底部想通的空洞个数
    int top[1001],bottom[1001];
    //top记录与顶面相交的洞的序号
    //bottom记录与底面相交的洞的序号
    
    int main()
    {
        int t;
        t=read();
        for(int i=1;i<=t;i++)
        {
            n=read();h=read();r=read(); //n个空洞,奶酪高为h,每个空洞的半径是r  
            bj=0;      
            int top_num=0;              //记录与顶面相交的洞有几个
            int bottom_num=0;           //记录与底面相交的洞有几个
            for(int j=1;j<=n;j++) f[j]=j;  //并查集初始化,自己的父亲是自己 
            for(int j=1;j<=n;j++)
            {
                a[j].x=read();          //空洞球心的坐标 
                a[j].y=read();
                a[j].z=read();            
                if(a[j].z+r>=h)         //判断这个点是否与顶面相交
                   top[++top_num]=j;
                if (a[j].z-r<=0)        //判断这个点是否与底面相交
                   bottom[++bottom_num]=j;
                for (int k=1;k<=j;k++)  //枚举之前的洞是否与这个洞相交,如果相交则合并集合
                { 
                    if (dis(a[j].x,a[j].y,a[j].z,a[k].x,a[k].y,a[k].z)<=2.0*r)  //判断两空洞是否相通 
                    {
                        int f1=find(j); //合并父亲的操作 
                        int f2=find(k);
                        if (f1!=f2) f[f1]=f2;
                    }
                }
            }
            for(int j=1;j<=top_num;j++) //判断和顶部相通的空洞与和底部相通的空洞是否为同一父亲 
            {
                for(int k=1;k<=bottom_num;k++)
                    if(find(top[j])==find(bottom[k])) 
                    {
                        bj=1; 
                        break;
                    }
                if(bj) break;
            }
            if(bj==1) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
        return 0;
    } 
  • 相关阅读:
    go语言基础之闭包捕获外部变量特点
    go语言基础之基匿名函数本语法和闭包
    go语言基础之回调函数
    go语言基础之函数类型
    go语言基础之递归实现数字累加
    go语言基础之递归函数的调用流程
    go语言基础之普通函数的调用流程
    maven install 打包 报错 Cannot run program "gpg.exe": CreateProcess error
    ELK&H3c监控篇
    机房监控中的网络设备监控到底是怎样的?
  • 原文地址:https://www.cnblogs.com/xcg123/p/11128705.html
Copyright © 2020-2023  润新知