2023天梯赛真题L2-3 锦标赛

原题链接:PTA | 程序设计类实验辅助教学平台千名教师建设,万道高质量题目,百万用户拼题的程序设计实验辅助教学平台https://pintia.cn/problem-sets/994805046380707840/exam/problems/1649748772841508874

这绝对是这届天梯赛里除了L3之外最难的题,L1和L2的其他题在题意理解明白的前提下都能很轻松的做出来,唯独这道题困扰了我好几天,考验的思维量不小。

理解明白题意之后不难发现,这道题考的是二叉树,而且是满二叉树,如果我们令一场比赛为一个节点,那么对于k轮比赛,树的深度就是k,树的节点个数就是2^{k}-1,对应2^k-1场比赛,最后要输出的其实就是全体叶子节点,也就是最后2^{k-1}个节点。

我对节点的定义为:

struct node {
    int win;    // 胜者的实力值
    int lose;   // 败者的实力值
}

那很明显,对于任意一个节点,都有win\geq lose。题目给了我们最后一场比赛的败者,以及最终的胜者,也就是告诉了我们根节点的胜者和败者,除此之外,它还给了我们其他所有节点上的败者的实力值,又因为题目中每场比赛是有顺序要求的,所以,对于每个节点,我们都可以直接按照输入顺序,从叶子节点开始,一层一层地将败者的实力值事先输入:

for (int i = r; i > 0; --i) {
    int n = 1 << (i - 1);
    int s = (2 << (i - 1)) - n;
    while (n--) {
        cin >> tree[s].lose;
        s++;
    }
}

最后将根节点的胜者输入:

cin >> tree[1].win;
if (tree[1].win < tree[1].lose) flag = false;
else solve(tree, 1, l);

这里要注意,判断一下最终胜者的实力值是否大于等于败者,如果不是那就是"No Solution",我这里选择用flag来进行记录,测试点三就是考这个。

重点是solve函数,我原本的思路是,对于第i和第i+1个节点(也就是第ii+1场比赛),取它的父节点,也就是第i/2个节点的胜者和败者,比较四个值之间的大小关系,然后把父节点上的胜者和败者放到两个子节点上合法的位置,如果两个位置都合法,也就是说两个子节点上的败者的实力值全部小于等于父节点上胜者和败者的实力值,那就随便选一种来放置。按照这个思路,我的代码轻松地被卡住了,卡在了最后两个测试点上(也算预料之内)。

这种思路的漏洞很明显,对于有两种合法的安排方式的情况,如果只是单纯的随便选一种,会影响到后续层上的节点,就是说,你选择了其中一种安排方案,使得这一层有解了,但这可能会导致下一层可能无解,但其实另一种安排方案可以使得下一层有解,但是却被你抛弃了,所以,你应该把两种方案都试一遍才行。

但是两种方案都试一遍的前提是我们有办法往回走,就是说当某一层出现问题的时候,要返回到之前层选择另一种方案来重新安排,要做到这一点,我就只能想到递归了。

首先,我们创建一个全局变量flag,用来记录解决问题的过程中是否出现"No Solution",flag==true则说明有解;否则说明无解。solve函数中,我们先写递归出口,即当当前要讨论的节点的编号已经大于总节点数,即已经到尽头,或者已经发现之前有无解的情况的出现时,直接返回,否则继续讨论。

solve的三个参数分别是树的指针,父节点编号和树的大小。每一次递归,我们都是根据父节点的编号去找两个子节点,然后把父节点上的两个值放到子节点上的合法位置,为了方便判断,我们事先比较一下两个子节点的败者的大小,然后讨论三种情况——无解、只有一种解、有两种解。对于无解的情况,我们将flag置为false就行了;只有一种解的情况也没什么好说的,按照唯一解的方案安排后递归调用solve来把两个子节点当作父节点继续向下解决问题就行了;重点是有两种解的情况,如果有两种解,我们先试用其中一种解,然后递归调用solve函数,若函数返回后发现flag变成false,也就是说下层出现了无解的情况时,我们将子节点的胜者调换位置,将flag设置为true,也就是尝试另一种方案,再递归解决后面的层,如果这一次flag还是变成false了,那就说明确实无解。

代码如下:

#include <bits/stdc++.h>
using namespace std;

struct node {
    int win, lose;
};

bool flag = true;

void solve(node *t, int root, int size) {
    if (root * 2 > size || !flag) return;
    int gt = t[root * 2 + 1].lose > t[root * 2].lose ? root * 2 + 1 : root * 2;
    int le = t[root * 2 + 1].lose > t[root * 2].lose ? root * 2 : root * 2 + 1;
    if (t[gt].lose > t[root].win || t[le].lose > t[root].lose) {
        flag = false;
    } else if (t[gt].lose > t[root].lose) {
        t[gt].win = t[root].win;
        t[le].win = t[root].lose;
        solve(t, root * 2, size);
        solve(t, root * 2 + 1, size);
    } else {
        t[le].win = t[root].win;
        t[gt].win = t[root].lose;
        solve(t, root * 2, size);
        solve(t, root * 2 + 1, size);
        if (!flag) {
            flag = true;
            swap(t[le].win, t[gt].win);
            solve(t, root * 2, size);
            solve(t, root * 2 + 1, size);
        }
    }
}

int main() {
    int r;
    cin >> r;
    int l = (2 << (r - 1)) - 1;
    node tree[l + 1];

    for (int i = r; i > 0; --i) {
        int n = 1 << (i - 1);
        int s = (2 << (i - 1)) - n;
        while (n--) {
            cin >> tree[s].lose;
            s++;
        }
    }
    cin >> tree[1].win;
    if (tree[1].win < tree[1].lose) flag = false;
    else solve(tree, 1, l);

    if (!flag) cout << "No Solution";
    else {
        int n = 1 << (r - 1);
        int s = (2 << (r - 1)) - n;
        while (n--) {
            cout << tree[s].win << ' ' << tree[s++].lose;
            if (n) cout << ' ';
        }
    }

    return 0;
}
  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值