PTA搜索专题部分题解

7-7 红色警报 (25分)
战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城市并不改变其他城市之间的连通性,则不要发出警报。

输入格式:
输入在第一行给出两个整数N(0 < N ≤ 500)和M(≤ 5000),分别为城市个数(于是默认城市从0到N-1编号)和连接两城市的通路条数。随后M行,每行给出一条通路所连接的两个城市的编号,其间以1个空格分隔。在城市信息之后给出被攻占的信息,即一个正整数K和随后的K个被攻占的城市的编号。

注意:输入保证给出的被攻占的城市编号都是合法的且无重复,但并不保证给出的通路没有重复。

输出格式:
对每个被攻占的城市,如果它会改变整个国家的连通性,则输出Red Alert: City k is lost!,其中k是该城市的编号;否则只输出City k is lost.即可。如果该国失去了最后一个城市,则增加一行输出Game Over.。

输入样例:

5 4
0 1
1 3
3 0
0 4
5
1 2 0 4 3

输出样例:

City 1 is lost.
City 2 is lost.
Red Alert: City 0 is lost!
City 4 is lost.
City 3 is lost.
Game Over.

这道题挺考验思维的(菜鸟的自我感觉),之前做的都是模板题,虽然专题是“搜索”,我知道要用bfs/dfs,但是还是没能做出来,赛后补题的时候仔细想了想才想出来。

解题思路:找出连通分量个数,如果去掉City x会使得连通分量增加至少2以上,就说明要发出红色警报了。(解题关键!!!)

#include <bits/stdc++.h>

using namespace std;

int n,m,k,sum1,sum2;
bool book[510],e[510][510];

void dfs(int u)//dfs找连通量个数
{
    int i;
    for(i=0; i<n; i++)
        if(e[u][i]==1&&book[i]==0)
        {
            book[i]=1;
            dfs(i);
        }
    return;
}

int main()
{
    int i,x,y,p;
    scanf("%d%d",&n,&m);
    for(i=0; i<m; i++)
    {
        scanf("%d%d",&x,&y);
        e[x][y]=e[y][x]=1;
    }
    for(i=0; i<n; i++)//先计算连通量个数
        if(book[i]==0)
        {
            dfs(i);
            sum1++;
        }
    scanf("%d",&k);
    int ans=k;
    while(k--)
    {
        sum2=0;
        scanf("%d",&p);
        for(i=0; i<n; i++)//把p点从“图”中删除,注意:这里不能用book[p]=1,因为后面我们会重新把book赋值0,如果这里用book[p]=1的话会对答案产生影响
            e[p][i]=e[i][p]=0;
        memset(book,0,sizeof(book));
        for(i=0; i<n; i++)//计算把p从“图”中删除后的连通量个数
            if(book[i]==0)
            {
                dfs(i);
                sum2++;
            }
        if(sum2-sum1>=2)
            printf("Red Alert: City %d is lost!\n",p);
        else
            printf("City %d is lost.\n",p);
        sum1=sum2;//把前一次的连通个数赋值给下一次循环
    }
    if(ans==n)
        printf("Game Over.\n");
    return 0;
}

7-10 功夫传人 (25分)
一门武功能否传承久远并被发扬光大,是要看缘分的。一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟子们的功夫就越弱…… 直到某一支的某一代突然出现一个天分特别高的弟子(或者是吃到了灵丹、挖到了特别的秘笈),会将功夫的威力一下子放大N倍 —— 我们称这种弟子为“得道者”。

这里我们来考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i代传人只能在第i-1代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z,每向下传承一代,就会减弱r%,除非某一代弟子得道。现给出师门谱系关系,要求你算出所有得道者的功力总值。

输入格式:
输入在第一行给出3个正整数,分别是:N(≤10​5​​)——整个师门的总人数(于是每个人从0到N−1编号,祖师爷的编号为0);Z——祖师爷的功力值(不一定是整数,但起码是正数);r ——每传一代功夫所打的折扣百分比值(不超过100的正数)。接下来有N行,第i行(i=0,⋯,N−1)描述编号为i的人所传的徒弟,格式为:K​i​​ ID[1] ID[2] ⋯ ID[K​i​​]

其中K​i​​是徒弟的个数,后面跟的是各位徒弟的编号,数字间以空格间隔。K​i​​为零表示这是一位得道者,这时后面跟的一个数字表示其武功被放大的倍数。

输出格式:
在一行中输出所有得道者的功力总值,只保留其整数部分。题目保证输入和正确的输出都不超过10​10​​。

输入样例:

10 18.0 1.00
3 2 3 5
1 9
1 4
1 7
0 7
2 6 1
1 8
0 9
0 4
0 3

输出样例:

404
#include<bits/stdc++.h>
using namespace std;
vector<int> mp[100005];
double ans;
void dfs(int s,double z,double r)
{
    int i;
    for(i=0; i<mp[s].size(); i++)
    {
        if(mp[s][0]==0)//得道者
        {
            ans+=mp[s][1]*z;//求和
            return ;
        }
        else
            dfs(mp[s][i],(z*(1-r/100)),r);//z得削弱
    }
}
int main()
{
    int n,i,m,num;
    double z,r;
    scanf("%d%lf%lf",&n,&z,&r);
    ans=0;
    for(i=0; i<n; i++)
    {
        scanf("%d",&m);
        if(m==0)//得道者
        {
            mp[i].push_back(0);
            scanf("%d",&num);
            mp[i].push_back(num);
        }
        else
        {
            while(m--)
            {
                scanf("%d",&num);//正常第子
                mp[i].push_back(num);
            }
        }
    }
    dfs(0,z,r);
    printf("%d\n",(int)ans);//输出
    return 0;
}

7-11 图着色问题 (25分)
图着色问题是一个著名的NP完全问题。给定无向图G=(V,E),问可否用K种颜色为V中的每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色?

但本题并不是要你解决这个着色问题,而是对给定的一种颜色分配,请你判断这是否是图着色问题的一个解。

输入格式:
输入在第一行给出3个整数V(0<V≤500)、E(≥0)和K(0<K≤V),分别是无向图的顶点数、边数、以及颜色数。顶点和颜色都从1到V编号。随后E行,每行给出一条边的两个端点的编号。在图的信息给出之后,给出了一个正整数N(≤20),是待检查的颜色分配方案的个数。随后N行,每行顺次给出V个顶点的颜色(第i个数字表示第i个顶点的颜色),数字间以空格分隔。题目保证给定的无向图是合法的(即不存在自回路和重边)。

输出格式:
对每种颜色分配方案,如果是图着色问题的一个解则输出Yes,否则输出No,每句占一行。

输入样例:

6 8 3
2 1
1 3
4 6
2 5
2 4
5 4
5 6
3 6
4
1 2 3 3 1 2
4 5 6 6 4 5
1 2 3 4 5 6
2 3 4 2 3 4

输出样例:

Yes
Yes
No
No

这道题虽然是搜索专题里的,这暗示我们要用bfs/dfs求解,但是做题的时候发现这道题的数据并不大,而且题意十分简单,于是我想直接暴力做,果然这道题暴力也是可以A的。

思路分析:先用两个数组x和y把点都存起来,然后再用一个数组book代表每个点的颜色,从样例可以看出来如果颜色数大于或者小于k都不算是图着色问题,所以我们用数组c来记录一下该种颜色之前出没出现过,最后从头开始遍历每个点如果那个点的颜色和相邻点的颜色相同就直接break,然后输出No,否则就是Yes。

#include <bits/stdc++.h>

using namespace std;

long long int x[1000010],y[1000010],book[1000010],c[1000010];//如思路分析所示

int main()
{
    long long int n,m,k,i,j,g,sum,flag;
    scanf("%lld%lld%lld",&n,&m,&k);
    for(i=0; i<m; i++)
        scanf("%lld%lld",&x[i],&y[i]);//记录所有点
    scanf("%lld",&g);
    while(g--)
    {
        sum=0;//初始化颜色数为0
        memset(c,0,sizeof(c));
        memset(book,0,sizeof(book));
        for(j=1; j<=n; j++)
        {
            scanf("%lld",&flag);
            book[j]=flag;//标记点j的颜色为flag
            if(c[flag]==0)//如果这个颜色是第一次出现
            {
                sum++;//颜色数+1
                c[flag]=1;//标记这个颜色,表明已经出现过了
            }
        }
        if(sum!=k)//如果颜色数不等于k就直接输出No(题意)
            printf("No\n");
        else
        {
            for(i=0; i<m; i++)//从头开始遍历所有点
                if(book[x[i]]==book[y[i]]&&book[x[i]]!=0&&book[y[i]]!=0)//如果相邻的两个点颜色相同就直接break
                    break;
            if(i==m)//如果前面break的话i就不会等于m了,所以这里的言外之意就是前面遍历的所有点中没有相邻的点颜色相同
                printf("Yes\n");
            else
                printf("No\n");
        }
    }
    return 0;
}

7-12 拯救007 (25分)
在老电影“007之生死关头”(Live and Let Die)中有一个情节,007被毒贩抓到一个鳄鱼池中心的小岛上,他用了一种极为大胆的方法逃脱 —— 直接踩着池子里一系列鳄鱼的大脑袋跳上岸去!(据说当年替身演员被最后一条鳄鱼咬住了脚,幸好穿的是特别加厚的靴子才逃过一劫。)

设鳄鱼池是长宽为100米的方形,中心坐标为 (0, 0),且东北角坐标为 (50, 50)。池心岛是以 (0, 0) 为圆心、直径15米的圆。给定池中分布的鳄鱼的坐标、以及007一次能跳跃的最大距离,你需要告诉他是否有可能逃出生天。

输入格式:
首先第一行给出两个正整数:鳄鱼数量 N(≤100)和007一次能跳跃的最大距离 D。随后 N 行,每行给出一条鳄鱼的 (x,y) 坐标。注意:不会有两条鳄鱼待在同一个点上。

输出格式:
如果007有可能逃脱,就在一行中输出"Yes",否则输出"No"。

输入样例 1:

14 20
25 -15
-25 28
8 49
29 15
-35 -2
5 28
27 -29
-8 -28
-20 -35
-25 -20
-13 29
-30 15
-35 40
12 12

输出样例 1:

Yes

输入样例 2:

4 13
-12 12
12 12
-12 -12
12 -12

输出样例 2:

No

思路分析:这道题需要用到一些数学方面的知识,说白了需要计算两点之间的距离,因为这道题作为搜索题,他的限制条件就是跳的距离,如果距离够的话就可以跳,否则不行。还有一个就是这个题的起跳点有很多并不是唯一的,只要007站在小岛上能跳到的鳄鱼头上的点都可以作为起跳点,所以我们需要把这些起跳点都记录下来,然后遍历都试一遍。另外这个题有负的点,所以我们不能用二维数组来存,因为二维数组没法存负的点,例如:e[-1][1]这样是不行的,所以我们可以设一个并行数组,和记录的点并行,用来判断这个点是否走过,最后最好在纸上画一下这个题的图(指正方形的水池和圆形的小岛)。

#include <bits/stdc++.h>

using namespace std;

int book[110],x[110],y[110],n,m,flag=0;//book就是并行标记数组,x数组和y数组分别记录鳄鱼头点的坐标。

int juli(int l,int r,int x,int y)//计算距离的函数
{
    return (l-x)*(l-x)+(r-y)*(r-y);
}

struct node//开一个结构体来存起跳点的坐标和标号,因为标号要和book数组并行所以也要记录下来
{
    int tx;
    int ty;
    int num;//标号
} a[110];

void dfs(int l,int r)
{
    int i;
    if(flag==1)//如果flag=1了,即代表已经至少有一条路可以让007逃跑了,所以就可以直接return了
        return;
    if(l>=50-m||l<=-50+m||r>=50-m||r<=-50+m)//如果最后007可以跳出水池的话(这里有点问题,我后来试了一下-50-m发现也可以AC,个人感觉这个判题的数据有点水)
    {
        flag=1;
        return;
    }
    for(i=0; i<n; i++)
        if(book[i]==0&&juli(l,r,x[i],y[i])<=m*m)//如果这个点之前没有跳过,并且距离足够007跳过去
        {
            book[i]=1;//标记这个点
            dfs(x[i],y[i]);//继续搜索
            book[i]=0;//回溯
        }
    return;
}

int main()
{
    int k=0,i;
    scanf("%d%d",&n,&m);
    for(i=0; i<n; i++)
    {
        scanf("%d%d",&x[i],&y[i]);//记录鳄鱼头的点
        if(x[i]*x[i]+y[i]*y[i]<=(15+m)*(15+m))//如果该点可以作为起跳点
        {
            a[k].tx=x[i];
            a[k].ty=y[i];
            a[k].num=i;
            k++;
        }
    }
    for(i=0; i<k; i++)//遍历所有起跳点
    {
        memset(book,0,sizeof(book));
        book[a[i].num]=1;//标记起跳点
        dfs(a[i].tx,a[i].ty);//搜索
        if(flag==1)
        {
            printf("Yes\n");
            break;
        }
    }
    if(i==k)
        printf("No\n");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值