PAT (Advanced Level) Practice 1101~1120

本文详细解析了PAT(Advanced Level)实践题中的四道题目,包括快速排序的实现、二叉树翻转的层序遍历与中序遍历、整数的因式分解以及供应链中最低价格的计算。通过深度优先搜索和并查集等算法,介绍了如何解决这些问题,并提供了详细的代码实现。
摘要由CSDN通过智能技术生成

1101 Quick Sort

此题为 PAT (Basic Level) Practice 1045 快速排序 的英文版,点击链接进入原题,题解可看 PAT (Basic Level) Practice 1045~1066


1102 Invert a Binary Tree

题意:

输入第一行是一个正整数 N,表明二叉树中结点的个数,结点按照 0 ~ N - 1 进行编号。随后 N 行信息,表明结点的左孩子和右孩子的编号,这 N 行信息分别对应第 0 到第 N - 1 个结点。现在要求你输出将这个二叉树翻转后的层序遍历序列和中序遍历序列。

思路:

在这里要注意,0 ~ N - 1 是结点的编号,题目的 N 行输入也对应了第 0 ~ N - 1 的结点信息,但是二叉树并非是按照结点的顺序进行排列的,也就是说根结点并不一定是编号为0的结点。那么弄清这点就好做了。

定义结构体 node 保存每个结点的左右孩子的编号,nodes 数组则保存二叉树中的所有结点,类似于静态数组,将结点的编号当做数组的下标,通过下标就能访问到结点的信息。通过字符串来保存每个结点的编号。因为层序遍历和中序遍历需要通过根结点开始,所以我们还要确定哪一个根结点。定义 flag 数组,在输入的过程中,将出现过的编号都标记为1,说明其存在父母结点,那么 flag 值为0的结点就是根结点。

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

struct Node {
    int lchild = -1, rchild = -1;
} nodes[10];

vector<int> in;

void InOrder(int root)
{
    if (nodes[root].lchild != -1) InOrder(nodes[root].lchild);      // 存在左孩子,递归遍历
    in.push_back(root);     // 放入中序序列
    if (nodes[root].rchild != -1) InOrder(nodes[root].rchild);      // 存在右孩子,递归遍历
}

void LayerOrder(int root)
{
    queue<int> q;
    q.push(root);           // 根结点入队
    while (!q.empty())
    {  
        if (q.front() != root) printf(" ");     // 除了根结点,打印其它结点前都要打印一个空格
        cout << q.front();  // 打印队首元素编号
        if (nodes[q.front()].lchild != -1) q.push(nodes[q.front()].lchild);   // 存在左孩子,左孩子入队
        if (nodes[q.front()].rchild != -1) q.push(nodes[q.front()].rchild);   // 存在右孩子,右孩子入队
        q.pop();            // 队首元素出队
    }
}

int main()
{
    int N, root = 0, flag[128] = {0};
    scanf("%d", &N);
    char a, b;
    for (int i = 0; i < N; ++i)
    {
        getchar();
        scanf("%c %c", &a, &b);                 // 用字符保存编号,scanf 能更好的控制输入格式
        if (a != '-') nodes[i].rchild = a - '0';
        if (b != '-') nodes[i].lchild = b - '0';
        flag[a - '0'] = flag[b - '0'] = 1;      // 将出现过的结点标记为1
    }
    while (flag[root]) ++root;  // 寻找根结点
    LayerOrder(root);           // 层序遍历
    InOrder(root);              // 中序遍历
    printf("\n%d", in[0]);
    for (int i = 1; i < N; ++i) printf(" %d", in[i]);
    
    return 0;
}

1103 Integer Factorization

题意:

输入只有一行:正整数 NK 以及 P。要求你打印出 N 的 K-P 因式分解中最大的序列。

N 的 K-P 因式分解:其实非常简单,就是将 N 用 K 个 P 次方的数的和表示出来,题目里已经给出了169的例子。问题在于,一个整数的 K-P 因式分解不止一个,需要你打印的是最大的那个序列。按照题目中的定义,1 2 3 4 的序列就比 1 2 3 5 要小(忽略这两个序列是否能得到同一个 N)。

思路:

深度优先搜索(DFS) 解决。

题目的意思相当于从众多整数中选出 K 个数,使得它们的 P 次方的和为 N。因为 N 最大为400,而 P 的最小值为2,因此可选择的数最大是20(当 N = 400, K = 1, P = 2 时只能选一个数,就是20)。而随着 P 变大,每一个可选的数就需要变小。所以不妨先定义一个数组 power 保存1 ~ 20中每个数的 P 次方的结果,即 power[i] = i P i ^ P iP,这样在选数的过程中就不需要重复计算某个数的 P 次方。

在 DFS 中,每次进入该层递归后,首先需要判断 now == K?,nowk 表示当前已选择了多少数。如果 nowk 等于 K 且平方的和 sum 也等于 N,就判断当前的整数序列 temp,是否大于 ans,而 ans 保存着我们找到的最大的序列。由于 vector 容器可以直接比较大小,所以 temp、ans 以及 power 都可以用 vector 来定义。

如果 now != K 或者 sum != N,表明选的数还不够,或者和不对,就需要接着执行后面代码。检查是否满足 nowk >= K,满足的话就不再进行递归(不能再选了),直接返回;否则,定义 i,如果 n = 0,i 就为 n + 1(即1),不等于0, 就为 n。为什么要这样,i 表明当前位置上要选择的数, i = 0 的话选择它就失去了意义,所以要进行以此判断。那为什么不继续查 sum 是否超过了 N 呢?在下面的一段以及经验中给出了回答。

我整个 DFS 的过程相当于固定了 K 个位置,对于每一个位置,分别枚举整数 1 ~ N(但是由前面知道我们不会真的枚举到 N,而是当 power[i] = 0 时就停止枚举,之所以用这个作为判断条件,是因为 power 是全局数组,其每个元素的初值都是0)。因此在定义完 i 之后,就用一个循环来枚举当前位置上所有可选的整数。则首先需要判断 sum + power[i] 是否超过了 N,如果超过了就没有选择 i 的必要,直接返回即可;否则就将 i 加入 temp,表明当前位置选择数 i,然后再去选择下一个位置的数,即 DFS(i, nowk + 1, sum + power[i])。退出下一层递归后,要先将数 i 弹出 tmep,才能退出当前的递归。

上面一段话相等于实现了这么一个效果:

第1个位置选择1,temp 中此时:1,和不满足选的数不够,进入循环,i = 1,进入下一层递归;
第2个位置选择1,temp 中此时:1 1,和不满足选的数不够,进入循环,i = 1,进入下一层递归;
第3个位置选择1,temp 中此时:1 1 1,和不满足选的数不够,进入循环,i = 1,进入下一层递归;
第4个位置选择1,temp 中此时:1 1 1 1,和不满足选的数不够,进入循环,i = 1,进入下一层递归;
第5个位置选择1,temp 中此时:1 1 1 1 1,和不满足,返回上一层递归的循环,i = 2,进入下一层递归;
第5个位置选择2,temp 中此时:1 1 1 1 2,和不满足,返回上一层递归的循环,i = 3,进入下一层递归;
·····

注意:

  • 尤其注意 K = 1 和 K = 400 这样一个临界点,但只要你整体思路是对的,这些小临界问题就很好解决。

经验:

  • 对于 DFS 来说,先判断能否满足条件再进入递归,比进入递归后再因为不满足条件而退出递归,所需要的开销要小很多。
#include <iostream>
#include <vector>
using namespace std;

int N, K, P;
vector<int> temp, ans, power(25);

void DFS(int n, int nowk, int sum)
{
    if (nowk == K && sum == N)          // 选择 K 个数且其平方和为 N
    {
        if (temp > ans) ans = temp;     // 选择更大的整数序列
        return;
    }
    if (nowk >= K) return;              // 选择的数到达了上限,退出当前递归
    int i = n == 0 ? n + 1 : n;         // 如果 n 为0,直接返回
    for (; power[i] != 0; ++i)
    {
        if (sum + power[i] > N) break;  // 和已经超过 N 时不需要再递归
        else
        {
            temp.push_back(i);          // 将整数 i 加入 temp
            DFS(i, nowk + 1, sum + power[i]);   // 选择 i
            temp.pop_back();            // 将整数 i 拿出 temp
        }
    }
}

int main()
{
    cin >> N >> K >> P;
    for (int i = 1; i <= N; ++i)
    {
        power[i] = 1;               // 初始化 i 的 P 次方为1
        for (int j = 0; j < P; ++j) power[i] *= i;  // 计算 i 的 P 次方
        if (power[i] > N) break;    // i 的 P 次方都比 N 大了,再往后就不用选了
    }
    DFS(0, 0, 0);
    if (ans.size())
    {
        cout << N << " = " << ans[ans.size() - 1] << "^" << P;
        for (int i = ans.size() - 2; i >= 0; --i)
            cout << " + " << ans[i] << "^" << P;
    }
    else cout << "Impossible";

    return 0;
}

1104 Sum of Number Segments

此题为 PAT (Basic Level) Practice 1049 数列的片段和 的英文版,点击链接进入原题,题解可看 PAT (Basic Level) Practice 1045~1066


1105


1106 Lowest Price in Supply Chain

题意:

输入第一行是三个数,第一个是正整数 N,表明树中所有成员(结点)的个数(编号 0 ~ N - 1),第二个数是浮点数 price,表明产品单价,第三个是浮点数 rise,表明增长率。随后一行是 N 个整数,第 i 个整数表明其是编号 i 结点的经销商,也就是双亲结点。题目规定了供应商(根结点)的编号为-1,产品单价每往下一层就增加 rise%。只有零售商(叶结点)能直接出售产品。现在要求你计算出最小的供应层数。并打印该层产品的单价,以及该层叶结点的数量。

思路:

深度优先搜索遍历解决。

题目其实求的就是树的最短路径。这一题的镜像题:1090 Highest Price in Supply Chain,题解可点击:PAT (Advanced Level) Practice 1081~1100

根据题目的输入用例,画出树的结构图后,其实可以发现,如果对层数从0开始编号,编号为1的层(也就是根结点的孩子结点所在的那一层)拿到产品的单价就是题目输入的原价,往下每一层就要上涨 rise%。可以这么做,对整棵树进行深度优先搜索遍历。

遍历到某个结点时,如果层数已经超过的最小层数就直接返回。否则检查其是否为叶结点,cnt 用于保存最小层数上叶结点的个数。如果当前层 depth 比最小层数 mindepth 小,就重置 cnt 为1,否则再检查 depth 是否等于 mindepth,是的话计数值加1;然后看 mindepth 是否小于 depth,是的话就更新最小层数。在这里,一定要先处理 cnt,因为先处理 mindepth 的话有可能 mindepth 的值就变了,导致 cnt 出错。

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

int mindepth = 100005, cnt = 0;
vector<int> childs[100005];

void DFS(int root, int depth)
{
    if (depth > mindepth) return;   // 如果往下深度更大就没必要继续执行,直接返回(剪枝操作)
    if (childs[root].size() == 0)   // 遇到新的叶结点,就检查是否为更短的路径
    {
        cnt = depth < mindepth ? 1 : (depth == mindepth ? cnt + 1 : cnt);   // 如果是最短路径就重置 cnt
        mindepth = depth < mindepth ? depth : mindepth;                     // 如果是最短路径就更新 mindepth
    }
    for (int i = 0; i < childs[root].size(); ++i)
        DFS(childs[root][i], depth + 1);
}

int main()
{
    int N, n, m;
    double price, rise;
    scanf("%d%lf%lf", &N, &price, &rise);
    for (int i = 0; i < N; ++i)
    {
        scanf("%d", &n);
        for (int j = 0; j < n; ++j)
        {
            scanf("%d", &m);
            childs[i].push_back(m);
        }
    }

    DFS(0, 0);
    printf("%.4lf %d", price * pow(1 + rise / 100, mindepth), cnt);

    return 0;
}

1107 Social Clusters

题意:

输入第一行是社交网络中的总人数 N(编号1 ~ N),随后 N 行数据对应这 N 个人。每一行第一个整数表明其爱好个数 K,随后 K 个数是其爱好的编号 h(1到1000的整数)。题目认定有着相同爱好的人属于一个社交群落(社交群落就是集合,比如编号 i 和 j 的用户喜欢爱好 m,编号 j 和 k 的用户喜欢爱好 n,他们也属于同一个集合)。现在要求你输出所有集合的个数,且按非递增的顺序输出每个集合中的人数。

思路:

并查集解决。

数组 father 记录每个用户的父亲结点的编号,hobby 数组保存喜欢某一个爱好的用户的编号,isroot 数组记录编号 i 的用户是否为一个集合中的根结点(只要非0就表明其为根结点),及其以 i 为根结点的集合中用户的个数。初始化 father 数组,令每一个用户的父编号都是自己。

输入的时候,记得过滤掉 K 后面的冒号,或者用 scanf 进行格式控制。初始化 hobby 数组为全0,表明没有一个爱好被用户喜欢。随后每输入一个 h,就先判断 hobby[h] 是否为0,是的话就令其为 i,表明用户 i 的爱好是 h。然后不管怎样,都需要检查用户 i 和喜欢爱好 h 的用户 hobby[h] 是否在一个集合中,FindFather 函数返回的是传入的编号所在集合的根结点的编号。如果 i 和 hobby[h] 不在一个集合中,但因为他们的爱好相同,需要让他们在一个集合中,所以就直接令用户 hobby[h] 所在集合的根结点的编号,成为 i 所在集合根结点的新父亲。也就是两个集合的合并过程。

最后枚举所有的用户 i,寻找其所在集合的根结点的 FindFather(i),再令对应的 isroot 值加1,就能统计出 i 所在集合中的人数。同时若 isroot 不为0,就令 ans 加1,ans 表明有多少个集合。

#include <iostream>
#include <algorithm>
using namespace std;

int father[1010], hobby[1010] = {0}, isroot[1010] = {0};

int FindFather(int x)
{
    while (x != father[x]) x = father[x];   // x 不断“向上”寻找根结点
    return x;
}

int main()
{
    int N, K, h, ans = 0;
    cin >> N;
    for (int i = 1; i <= N; ++i) father[i] = i; // 初始化 father 数组
    for (int i = 1; i <= N; ++i)
    {
        cin >> K;
        getchar();  // 过滤掉冒号
        while (K--)
        {
            cin >> h;
            hobby[h] = hobby[h] ? hobby[h] : i;         // 第 h 个爱好如果没人喜欢,就令第 i 人喜欢它
            if (FindFather(i) != FindFather(hobby[h]))  // 合并两个集合
                father[FindFather(i)] = FindFather(hobby[h]);
        }
    }
    for (int i = 1; i <= N; ++i)
    {
        ++isroot[FindFather(i)];        // 对根结点的 isroot 值加1
        ans += isroot[i] ? 1 : 0;       // ans 计数加1
    }
    sort(isroot + 1, isroot + N + 1);   // 默认的排序是从小到大
    cout << ans << "\n" << isroot[N];   // 因此需要从 isroot 数组末尾开始输出
    for (int i = N - 1; i >= N - ans + 1; --i)
        cout << " " << isroot[i];
    
    return 0;
}

1108


1109


1110

1111


1112


1113


1114


1115


1116


1117


1118


1119


1120


一定要自己写一遍哦~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值