GPLT 团体程序设计天梯赛 2022年 - 模拟赛 赛后复盘 附思路与代码注释

前言:2022年的天梯赛模拟赛难度中等,L1中规中矩、L2常规难度、L3较为简单。

参考资料:团体程序设计天梯赛-练习集。

接下来直接上技巧与代码,全文共19602字,配合右边的目录阅读体验更佳~

如果这篇文章被自动设置为VIP可见或者付费,请尽快联系我。

L1-基础级(100分)

题目中规中矩,模拟赛挖坑的题是L1-8,枚举方法错误可能会超时。

但L1部分基本上还是涵盖了近年来L1的所有考点:

简单输出、顺序语句、分支语句、循环语句、字符串处理,较复杂模拟。

建议学习set、unordered_set、map、unordered_map,对部分题目有显著效果。

L1必有至少一道题目挖坑。(例如审题、一些边界问题、暴力做法超时等)

L1的绝大多数题目,用任何语言写出正解不会超时。

C++选手的优秀手速(自己可以尝试训练到这个标准,可以给L2和L3多出很多做题时间):

获奖过天梯赛的选手:达到95分:30分钟。达到100分:40分钟。

未获奖过天梯赛的选手,在50-60分钟内得到90-95分就很不错;熟练后可按上述要求练习手速。

L1-1 自动编程(5分)

思路:用int存一个变量,之后按照输出格式输出字符串即可。

提示:这道题可以存你的模板/头文件,方便直接复制。

C++代码:L1-1 自动编程 (5 分)_本题就请你写一个自动编程机,对任何一个要输出的整数 n,给出输出这个整数的 pytho-CSDN博客

Python代码:

from heapq import heappush,heappop #优先队列
from bisect import bisect,bisect_left #二分,类似C++的lower_bound
from math import sqrt,ceil,floor #开根号,向上取整,向下取整
from collections import deque #双端队列,C++也有
import sys #这个模块很有用
import time #time.time()相减可以算运行时间,在蓝桥杯赛制下有用 检测是否超时
sys.setrecursionlimit(1000010) #手动设置递归深度,解除递归限制 默认递归深度999
sys.set_int_max_str_digits(1000010) #设置数字最大位数(更高的高精度)
inpu = sys.stdin.readline #快读,字符串末尾带换行,用strip()方法可以去换行
prin = sys.stdout.write #快写,不会自带换行,要手动换行
#上面是模板,可以复制到其他题目
a = int(input())
print("print(%d)"%a)

#C++模板,记得在PTA上的Python3语言环境同时按下Ctrl+/,解除注释,然后切换到C++语言
#天梯赛可以用不同的语言交题目(例如一道题用Python3,另一道题用C++)
# #include
# using namespace std;
# typedef long long ll;
# #define append push_back
# #define print printf
# int main()
# {
#     ios::sync_with_stdio(false);
#     cin.tie(0);
#     cout.tie(0);
#     return 0;
# }

L1-2 太神奇了(5分)

思路:输入两个数字,输出他们的和减一。出题人贴心地说明:因为贴子里说“明年全世界所有的人都同岁”,所以发贴是在今年

提示:无。

C++代码:L1-2 太神奇了 (5 分)_“告诉大家一个神奇的消息,太神奇了:明年全世界所有的人都同岁,全部都等于2022。-CSDN博客

Python代码:

from heapq import heappush,heappop
from bisect import bisect,bisect_left
from collections import deque
from math import ceil,floor,sqrt
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write

a,b = map(int,input().split()) #python一行读取多个变量
print(a+b-1) #记得-1

L1-3 洛希极限(10分)

原题链接:L1-067 洛希极限 - 团体程序设计天梯赛-练习集

思路:题目说明”比值开 3 次方“,其实不需要这么做;因为题目提供的输入值已经是开 3 次方之后的值了。根据题目,如果材质的数码是0,则最终结果是开 3 次方的值乘以2.455;如果材质的数码是1,则最终结果是开 3 次方的值乘以1.26。然后和最终结果比较:如果最终数值大于题目提供的半径,输出T_T;否则输出^_^

提示:这道题不卡边界,也就是测试用例没有”刚好等于“的情况;判断时使用小于、小于等于(或大于、大于等于)均可通过。

C++代码:【2020天梯赛】L1-3 洛希极限 (10分)_输入在一行中给出 3 个数字,依次为:大天体密度与小天体的密度的比值开 3 次方后计-CSDN博客

Python代码:

from heapq import heappush,heappop
from bisect import bisect,bisect_left
from collections import deque
from math import ceil,floor,sqrt
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write

a,b,c = map(float,input().split()) #要用浮点数读入
if b == 0:
    bei = a * 2.455 #输入0,乘以题目给的值
else:
    bei = a * 1.26 #输入1,乘以题目给的值
if bei <= c: #判断乘之后的值和半径比较
    print("%.2lf ^_^"%bei)
else:
    print("%.2lf T_T"%bei)

L1-4 吃鱼还是吃肉(10分)

原题链接:L1-063 吃鱼还是吃肉 - 团体程序设计天梯赛-练习集

思路:用if-else语句判断每个情况,按照题意直接一鼓作气判断所有情况即可。

提示:无。

C++代码:L1-063 吃鱼还是吃肉(分数10)-CSDN博客

Python代码:

from heapq import heappush,heappop
from bisect import bisect,bisect_left
from collections import deque
from math import ceil,floor,sqrt
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write

n = int(input())
for i in range(n):
    xb,sg,tz = map(int,input().split()) #性别 身高 体重
    if xb == 1: #性别为男
        if sg > 130:
            print("ni li hai!",end = ' ') #将默认的输出后换行改为输出后空格 下同
        elif sg < 130:
            print("duo chi yu!",end = ' ')
        else:
            print("wan mei!",end = ' ')
        if tz > 27:
            print("shao chi rou!")
        elif tz < 27:
            print("duo chi rou!")
        else:
            print("wan mei!")
    else: #性别为女
        if sg > 129:
            print("ni li hai!",end = ' ')
        elif sg < 129:
            print("duo chi yu!",end = ' ')
        else:
            print("wan mei!",end = ' ')
        if tz > 25:
            print("shao chi rou!")
        elif tz < 25:
            print("duo chi rou!")
        else:
            print("wan mei!")

L1-5 不变初心数(15分)

思路:对于每一个数,乘积最多不超过9*10^{5},也就是6位数。假设6位数都是9,那么乘积每位的和最多会是54,所以我们开一个大小至少为55的数组即可。之后对原数字乘以2到9经过数位和操作后初心数字进行枚举。例如100000乘以9得到900000,再逐位相加,得到初心数字是9。

假设我们存放每一位的数组是int b[55];得到的初心数字变量名是tmp,则统计初心数字的语句是:b[tmp] += 1;之后从0到54循环统计b[i]非0的数字个数(即出现了多少个初心数)。如果出现的数字个数不唯一,输出NO;否则记录那个数字,并输出。

也可以直接用set或者unordered_set来存储出现的数字个数。用size判断,最后输出NO或者唯一的数字。

提示:因为是多组用例,所以每一组用例判断之前需要把数组的所有数清零;如果使用set或者unordered_set,则需要把容器清空(C++用swap交换两个容器的时间复杂度是O(1)的,所以可以在函数内创建临时容器来交换全局变量的容器)。

C++代码:PTA 7-2 不变初心数(15分)思路简单_数的变化pta-CSDN博客

Python代码:

from heapq import heappush,heappop
from bisect import bisect,bisect_left
from collections import deque
from math import ceil,floor,sqrt
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write

def chuxin(x):
    ss = set() #在函数内新建一个set python的set无序
    for i in range(2,10): #python循环左闭右开,所以实际上枚举2,10-1 -> 2,9
        tmp = x * i
        su = 0
        while tmp > 0: #求各位和
            su += tmp % 10
            tmp //= 10
        ss.add(su)
    if len(ss) == 1: #是否唯一
        print(*ss) #print里面把列表、set、deque打上星号 直接输出元素 中间空格隔开
        #如果中间要用其他东西隔开 例如. 则用print(*ss,sep = '.'),其他间隔字符同理
    else:
        print("NO")
n = int(input())
for i in range(n):
    x = int(input())
    chuxin(x)

L1-6 字母串 (15分)

思路:直接从下标0到倒数第二位逐个枚举情况即可。题目中的”注意 a 没有前一个字母, Z 也没有下一个字母。“指的是当前字母不适用这个判断条件。

提示:1.题目中的”当然也可以什么都不写,就结束这个字母串。"说明最后一个字母不需要判断

           2.记得开快读快写,或者用scanf处理,效率会更高一些。

          3.这道题估计是近几年最简单的字符串处理题目了。

C++代码:字母串 (15 分) C语言_英语老师要求学生按照如下规则写一串字母: 规则1、如果写了某个大写字母,下一个就-CSDN博客

Python代码:

from heapq import heappush,heappop
from bisect import bisect,bisect_left
from collections import deque
from math import ceil,floor,sqrt
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline #快读 自带换行符 用strip可以解决
prin = sys.stdout.write #快写 不自带换行符

def pd(a):
    for i in range(len(a)-1): #枚举到倒数第二个就行 循环左闭右开 所以是len(a)-1
        if a[i].isupper(): #isupper islower isalpha isdigit isalnum isspace 和C++很像
            tmp = ord(a[i]) #python没有类似C++自动转char为int的类型
            tmp2 = ord(a[i+1]) #ord是字符转ASCII chr是ASCII转字符
            if tmp2 - tmp != 32 and tmp2 - tmp != 1: #不是当前小写 也不是下一个大写
                return 0
        elif a[i].islower():
            tmp = ord(a[i])
            tmp2 = ord(a[i+1])
            if tmp - tmp2 != 32 and tmp - tmp2 != 1: #不是当前大写 也不是下一个的前小写
                return 0
    return 1
#inpu和prin是快读快写的缩写 不是打错了
n = int(inpu())
for AAA in range(n):
    a = inpu().strip()
    if pd(a) == 1:
        prin("Y\n")
    else:
        prin("N\n")

L1-7 矩阵列平移(20分)

思路:按照题意模拟即可,如果这一次要移动i格,先把这一列的前n-i个数存到另一个数组,之后将这一列的前i格用x填充剩下n-i格将另一个数组的数字放入剩余位置逐个填充。最后对每一行求和即可。

提示:不能从前往后平移这一列的前i格数字到后i格数字,因为可能有重叠的部分,也就导致原先下标较后的位置对应的数字会被已经平移的数字替换,导致答案错误。(不过根据这一篇题解,从后往前的平移可以避免重叠数字的情况,那也不需要额外设置数组)。

C++代码:L1-7 矩阵列平移 (20 分)【c语言】-CSDN博客

Python代码:

from heapq import heappush,heappop
from bisect import bisect,bisect_left
from collections import deque
from math import ceil,floor,sqrt
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write

n,k,x = map(int,inpu().split())
a = []
for i in range(n):
    a.append(list(map(int,inpu().split()))) #直接读一行数据生成列表
jishu = 0 #计数器,每次平移1到k步
for i in range(1,n,2): #下标为0开始,偶数行的下标其实是1,3,5,7,9... 步长为2
    lie = [] #临时列表(数组)
    yi = jishu%k + 1 计数器%k得到的范围是[0,k-1],加上1的范围刚好是[1,k]
    for j in range(n-yi): #先把前面要移动的元素塞入列表
        lie.append(a[j][i])
    for j in range(yi):
        a[j][i] = x #全部替换为x
    po = 0 #设定起始位置指针(下标)
    for j in range(yi,n):
        a[j][i] = lie[po] #对应的位置塞入临时数组对应的下标
        po += 1
    jishu += 1 #计数器,决定下一步移动的步数
#调试用
# for i in a: 
#     print(*i)
ans = [] #存储每行和
for i in range(n):
    su = 0 #求和操作
    for j in a[i]:
        su += j
    ans.append(su)
print(*ans) #print星号可以直接以中间为空格为间隔输出 其他字符用sep控制 参见初心数那道题

L1-8 均是素数(20分)

思路:直接用朴素筛法,或者用埃氏筛或者欧拉筛预处理1到1001000(因为是p*q+r)的质数,然后将[m,n]之间(含边界)的所有质数取出来放入一个vector,3重循环判断p,q,r的三种情况是否都成立即可。

提示:1.如果不使用朴素筛法,可以用筛法提供的bool数组直接判断这个数是不是质数,也可以使用unordered_set或者set判断。

           2.这道题用Python会超时。

           3.简单的解法可以参考博客:L1-8 均是素数 (20 分)-CSDN博客

C++代码:

//这里提供了欧拉筛做法
#include
using namespace std;
typedef long long ll;
#define append push_back
#define print printf
int pr[1005005]; //表示存的质数
bool st[1005005]; //表示是否是合数,true是合数,false是质数
vector p; //存放枚举用的质数
unordered_set su; //unordered_set找一个元素时间复杂度O(1)
int pos = 0;
bool fi(int x) //查找这个数在不在集合(其实就是判断质数)
{
    //其实当时想复杂了 可以这样做:如果st[x] == 0返回true st[x] == 1返回false
    if(su.find(x) != su.end()) return true;
    return false;
}
void init()
{
    for(int i=2;i<=1005002;i++)
    {
        if(!st[i]) pr[pos++]=i; //不是合数 存到质数里面,然后下标+1
        for(int j=0;j1005002) break; //超过预设范围
            st[i*pr[j]] = 1;
            if (i % pr[j] == 0) break; //说明这个因子被筛过了 之后这个数的因子也不必再筛
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int a,b;
    long long ans = 0;
    cin>>a>>b;
    init();
    for(int i=0;i=a&&pr[i]<=b) p.append(pr[i]); //塞入区间内素数
    for(int i=0;i

Python代码:无

L2-进阶级(100分)

2022年的模拟赛L2-1、L2-2、L2-4是简单题,L2-3略有难度(如果不熟悉的话)。

L2部分基本上还是涵盖了近年来L2的考点(基本上每年都是这几个考点):

STL容器、复杂模拟、数据结构(树考的偏多)、搜索(或图论),对应题号1-4。

建议多练PTA上的团体程序设计天梯赛的练习集的L2题目,熟练之后会好做很多。

L2的L3一些题目,用其他语言写正解可能会超时,所以最好用C++。

这里给出L2和L3可以用Python的判断时间复杂度方法:

时间复杂度总和少于10^{5}的,除了频繁对列表进行操作(例如for循环、修改值等),放心使用。

除了图论外,给Python延长了时限的题目,放心使用。

C++选手的优秀手速(自己可以尝试训练到这个标准,可以给L3多出很多做题时间):

天梯赛获奖过的选手:达到85分:50分钟。达到100分:70分钟。

天梯赛未获奖过的选手,如果L1在1小时内获得了90分以上,在剩余2小时内在L2和L3得到85分及以上就得到了国家三等奖;熟练后可按上述要求练习手速。

L2-1 盲盒包装流水线(25分)

思路:先开一个数组记录编号出现的次序,再开大小为100000的数组(用队列也可以),再对于每一行类型直接压入栈中,之后弹出来再将对应的编号作为下标赋值为类型即可。赋值之后进行查询操作,直接将查询的值作为数组下标,输出对应的值即可。(如果上述采用队列操作,也是类似的把类型映射到数组下标中)。

提示:1.如果找到规律,甚至不需要栈也可以完成。

           2.用栈与队列的解法可以参考博客:2022 团体程序设计天梯赛 模拟赛 L2-1 盲盒包装流水线 (25 分)_l2-1 包装机csdn-CSDN博客

C++代码:

#include
using namespace std;
typedef long long ll;
#define append push_back
#define print printf
bool bh[100005];
int a[100005];
int b[100005];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n,m;
    cin>>n>>m;
    int pos = 1;
    for(int i=1;i<=n;i++) cin>>a[i],bh[a[i]]=1; //输入出现过的编号并标记为出现过
    int hang = n/m; //输入的行数
    for(int i=1;i<=hang;i++)
    {
        for(int j=1;j<=m;j++)
        cin>>b[a[i*m-j+1]]; //这个式子就是第i行第j个的下标了 输入类型
    }
    int k;
    cin>>k;
    for(int i=1;i<=k;i++)
    {
        int bb;
        cin>>bb;
        if(!bh[bb]) cout<<"Wrong Number\n"; //没出现过的编号输出Wrong Number
        else cout<

Python代码:无

L2-2 点赞狂魔(25分)

原题链接:L2-021 点赞狂魔 - 团体程序设计天梯赛-练习集

思路:用结构体保存一个人的名字、点赞数量、平均点赞数。平均点赞数=点赞数量/总出现的标签数量。总出现的标签数量可以用数组统计出现次数非0的标签数(因为K最多只有1000),也可以用unordered_set或者set统计总标签数更方便。

最后给结构体排序,需要自定义写cmp函数,也可以在结构体里面写operator来实现。这道题有一个不算坑的坑点:若不足3人,则用-补齐缺失,例如mike jenny -就表示只有2人。所以要特判总人数。

提示:注意结构体自定义排序cmp代码,务必掌握

C++代码:

#include
using namespace std;
typedef long long ll;
#define print printf //python用习惯 避免写错重新改
#define append push_back //同上
struct Node
{
    string mz;
    int zanshu;
    double pj;
} res[1010];
bool cmp(Node A,Node B) //自定义结构体比较函数
{
    if(A.zanshu==B.zanshu) return A.pj < B.pj; //一样就平均数升序
    return A.zanshu > B.zanshu; //赞数降序
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        string a;
        cin>>a;
        res[i].mz=a;
        int k;
        cin>>k;
        unordered_set t; //无序集合,插入查询删除平均情况下均为O(1)
        for(int ii=1;ii<=k;ii++)
        {
            int x;
            cin>>x;
            t.insert(x); //将当前标签塞入unordered_set 他和set能保证里面元素不重复
        }
        //cout< ans; //存答案名字
    for(int i=1;i<=min(n,3);i++) //人数可能不足3人
        ans.append(res[i].mz); //塞入名字 这里用append其实被define过了
    while(ans.size()<3) ans.append("-"); //特判人数不足
    for(int i=0;i

Python代码:无

L2-3 浪漫侧影(25分)

思路:对熟练的选手和熟练数据结构选手来说难度不高,按照中序遍历和后序遍历建树之后设与节点层数相同数量的vector记录每一层从左到右的节点,然后再从根BFS一遍,先左孩子(若有)再右孩子(若有)。每出队一个节点,节点的对应层数就要塞入这个节点。这样做保证每一层的节点从左到右。之后直接输出每一层的最后被塞入一个节点(即下标为v[i].size()-1的节点),组合起来就是右视图;每一层的第一个被塞入的节点(即下标为0的节点),组合起来就是左视图

提示:1.中序遍历与后序遍历建树方法:后序遍历的最后一个节点一定是根节点(左右根),中序遍历的树根一定能把树划分为左子树和右子树(左根右)。找到那个中序遍历的那个根节点,把中序遍历和后序遍历划分为左子树和右子树。

之后根据左子树(或右子树)大小划分前序遍历的建树范围。得到左子树和右子树的范围后,递归执行左子树和右子树建树操作,直到树空为止。对于每棵树,左子树的根就是根节点的左孩子,右子树的根就是根节点的右孩子。注意左子树和右子树可能是空的。

           2.前序遍历与中序遍历建树方法:前序遍历的第一个节点一定是根节点(根左右),中序遍历的树根一定能把树划分为左子树和右子树(左根右)。找到那个中序遍历的那个根节点,把中序遍历和前序遍历划分为左子树和右子树。

之后根据左子树(或右子树)大小划分前序遍历的建树范围。得到左子树和右子树的范围后,递归执行左子树和右子树建树操作,直到树空为止。对于每棵树,左子树的根就是根节点的左孩子,右子树的根就是根节点的右孩子。注意左子树和右子树可能是空的。

           3.上述两种方法生成的树肯定都是唯一的(前提是序列合法)。但前序遍历和后序遍历建树不一定唯一。举一个例子:前序遍历:1 2;后序遍历:2 1。此时1是根,2可能是左孩子,也可能是右孩子,总共有2种可能生成的树。

           4.如果发现这些这道题的前3条提示比较难懂,那么推荐你去看这篇博客,讲的比较透彻:二叉树——根据先序(后序)、中序 建树,超详讲解。_如何利用先序和中序创建二叉树-CSDN博客

           5.天梯赛L2常考关于树的题目,且基本都出在L2-3。建议根据团体程序设计天梯赛的练习集多加练习这一类代码,是你能在赛场上快速熟练写出这类题的一条秘诀。

C++代码:

#include
using namespace std;
typedef long long ll;
#define append push_back //习惯用python 就用append代替push_back
#define print printf
vector ceng[25];
int n;
int maxde = 0; //maxdepth 最大深度的缩写
int zhong[25];
int hou[25];
int lc[25];
int rc[25];
int build(int zpos1,int zpos2,int hpos1,int hpos2)
{
    if(zpos1 > zpos2 || hpos1 > hpos2) return -1; //左位置大于右位置 树空
    int rt = hou[hpos2]; //根是后序遍历的最后一个
    int pos = 1;
    while(zhong[pos] != rt) pos ++; //找中序遍历根所在的下标
    lc[rt] = build(zpos1,pos-1,hpos1,hpos1+pos-zpos1-1); //划分左子树序列范围
    rc[rt] = build(pos+1,zpos2,hpos1+pos-zpos1,hpos2-1); //划分右子树序列范围
    return rt;
}
void bfs(int root)
{
    queue q; //储存节点
    queue b; //储存步数
    //其实可以用结构体或者pair实现一个队列 但比较麻烦 所以我选择分开写
    //根节点在第1层
    q.push(root);
    b.push(1);
    while(q.size())
    {
        int u = q.front();
        int bs = b.front(); //bs -> 步数的缩写
        maxde = max(maxde,bs); //统计最大层数
        q.pop();
        b.pop();
        ceng[bs].append(u); //将u节点塞入对应的层数vector 这里用append的原因见上
        //先左孩子再右孩子
        if(lc[u]!=-1) q.push(lc[u]),b.push(bs+1);
        if(rc[u]!=-1) q.push(rc[u]),b.push(bs+1);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    memset(lc,-1,sizeof(lc)); //左孩子初始化为空
    memset(rc,-1,sizeof(rc)); //右孩子初始化为空
    cin>>n;
    //输入中序后序
    for(int i=1;i<=n;i++) cin>>zhong[i];
    for(int i=1;i<=n;i++) cin>>hou[i];
    int root = build(1,n,1,n); //建树操作
    //cout<

Python代码:无

L2-4 哲哲打游戏(25分)

原题链接:L2-040 哲哲打游戏 - 团体程序设计天梯赛-练习集

思路:这道题比L2的其他题目都简单,直接模拟3种操作即可。用vector或者其他容器记录选择位置后,用大小比100略大的数组作为存档档位数(因为下标从1开始)。

提示:1.选择、存档和读档的操作都是从下标1开始的,尝试切换到下标0开始配合vector,或者采用其他的策略。

           2.别忘了输出最后到达的那个编号。

           3.Python选手记得开快读快写,提高效率(不开这道题其实也不会超时)。

C++代码:L2-040 哲哲打游戏 (25 分)动态数组 纯模拟 - 林动 - 博客园

Python代码:

from collections import deque
from math import sqrt,ceil,floor
import sys
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write
a = []
dang = [0] * 105 #题目说明最多100个存档档位 可以开大一些避免意外越界
n,m = map(int,inpu().split()) #inpu和prin是快读快写
a.append([0]) #填充下标为0的行
cur = 1
for i in range(n): #这样就能从下标为1的开始存选项了
    #因为第一个数字是选择的数量,在整行读入的python意义不大,但刚好可以用来填充下标0
    a.append(list(map(int,inpu().split())))
for i in range(m):
    l,r = map(int,inpu().split()) #选择编号和位置
    if l == 0:
        cur = a[cur][r] #做选择
    elif l == 1:
        prin(str(cur)) #快写要转为str才能输出
        prin('\n') #快写不带换行 要自己输入
        dang[r] = cur #存档
    elif l == 2:
        cur = dang[r] #读档
prin(str(cur)) #输出最后到达的那个编号

L3-登顶级(90分)

2022年模拟的L3前面两道题相对容易一些。L3-1是多状态转移Dijkstra+字符串转下标处理;L3-2是接近模板的背包DP;L3-3基本上不会有多少人写出来。

对于正常的选手来说,L3-1、L3-2、L3-3近几年的得分“风格”有两种:

           1.L3-1接近满分,L3-2用暴力+骗分可以得到20分及以上,L3-3基本不得分;

           2.L3-1接近满分,L3-2和L3-3暴力+骗分总共可以得到20分及以上。

建议多练习骗分技巧,并争取写出80%的L3-1正解;剩下20%能拿多少分就拿多少分。L3-2和L3-3能暴力就暴力;不能暴力就尝试骗分。

关于L3的其他语言超时问题,参见L2介绍。

如果前面大部分的分已经获得,还剩下70分钟及以上做L3的,属于优秀手速的选手。

L3-1可以尝试写出接近正解的答案;L3-2和L3-3建议花5-10分钟想最暴力的思路(无论时间复杂度多高,都能过0号样例,分值最大),如果想不到思路,前面写的也差不多,可以从0到50一个一个(当然有时间可以枚举更多)输出答案骗分。

L3-1 直捣黄龙(30分)

原题链接:L3-011 直捣黄龙 - 团体程序设计天梯赛-练习集

思路:题目要求:“以最快的速度占领敌方大本营。当这样的路径不唯一时,要求选择可以沿途解放最多城镇的路径。若这样的路径也不唯一,则选择可以有效杀伤最多敌军的路径。”其实就是说,如果“时间”这个距离不是最短,更新“时间、城镇、杀伤敌军”这3个参数;因为“时间”这个距离是“第一关键字”,必须保证他最短;同理,如果“时间”关键字一样“城镇”却不是解放最多的,则更新“城镇、杀伤敌军”这两个参数;如果“时间、城镇”关键字都一样,则要保证“杀伤敌军”最多,再根据这个参数更新在这里,”时间“是第一关键字,“城镇”是第二关键字,“杀敌”是第三关键字。也就是说,关键字的优先级代表了优先更新的关系。

看到这里是不是想起了结构体的自定义排序函数cmp?没错,和这个原理很类似,多状态Dijkstra转移是用与自定义排序函数类似的形式写出来的。于是就可以解决第一个难点:多状态Dijkstra。

第二个难点:如何求路径条数?设cnt数组初始化为0,起点下标的cnt设为1,之后每一次更新从t走到j时,判断第一关键字距离;如果第一关键字距离能更新到更优,则cnt[j]=cnt[t];如果与当前的第一关键字距离dis[j]相等,则cnt[j]+=cnt[t];最终输出终点下标的cnt即可。

第三个难点:怎么记录路径?我的观点是:先初始化前驱pre数组并将所有数字初始化为-1。再跑一遍Dijkstra,如果遇到了一次更新操作(无论是第几关键字更新),假设路径从t到j,则j的前驱就更新为p。因为被成功更新说明这个路径更优,所以遇到更新成功操作,前驱就要立即改变。之后就获得了一条从终点一直走向起点的路径链。用while循环的方式获取当前节点,并将当前节点塞入栈中,当前节点通过pre数组变为前驱节点,直到当前节点为-1为止(因为起点的pre就是-1,不会再有更多的节点)。这样出栈后的序列就是起点到终点的路径。

还有一个难点:怎么把字符串映射到下标0到n-1(或1到n)?用两个unordered_map加上int idx=0;就可以解决。其中一个unordered_map从string转到int,把字符串转化为节点;还有一个从int映射到string,将节点映射成字符串(输出路径用)。每遇到一个没见过的字符串,建立int和string的双向映射关系,之后idx ++。最后跑一遍Dijkstra出结果。

提示:1.如果前面的关键字能更新,比他后面的关键字都必须更新。

           2.Dijkstra选择距离最近的点必须选择第一关键字距离最小的

           3.如果没时间写这道题,或者不会写,直接用Dijkstra求最短路,同时转移这几个关键字,或许可以骗分。

           4.天梯赛的图论(特别是Dijkstra、Floyd)每年必考,基本是多状态更新,或者其他加了花样的操作,但本质上还是最短路的探索。建议多练PTA上的团体程序设计天梯赛的练习集,也有一定的帮助。

C++代码:

#include
using namespace std;
typedef long long ll;
#define append push_back
#define print printf

unordered_map its;
unordered_map sti;
int n,m,s,t;
int idx = 0;
int dis[205];
int dis2[205];
int dis3[205];
int f[205][205];
int cnt[205];
int ren[205];
int pre[205];
bool st[205];

void dij(int s,int e)
{
    //起点初始化为0
    dis[s] = 0;
    dis2[s] = 0;
    dis3[s] = 0;
    cnt[s]=1; //设起点到起点的最短路个数为1
    //Acwing的y总朴素Dijkstra模板
    for(int i=1;i<=n;i++) //迭代n-1次就够了 这里为了方便写了n次
    {
        int t = -1; //表示暂未选点
        for(int j=0;jdis[t]+f[t][j]) //第一关键字(时间)距离更优
            {
                //路径数变成前驱路径数 因为到前驱的+这一条边的才是最短路
                cnt[j]=cnt[t];
                dis[j]=dis[t]+f[t][j]; //更新第1关键字距离
                dis2[j]=dis2[t]+1; //更新第2关键字距离
                dis3[j]=dis3[t]+ren[j]; //更新第3关键字距离
                pre[j]=t; //更新满足要求的前驱
            }
            else if(dis[j]==dis[t]+f[t][j]) //第一关键字相等
            {
                cnt[j]+=cnt[t]; //加上前驱的路径数 因为之前遇到过相等的
                //又有节点相等,所以可以累加
                if(dis2[j]>n>>m>>ss>>tt; //输入城市数、路径条数、起点终点
    //起点终点的字符串没出现过,进行映射操作
    if(!sti.count(ss)) sti[ss]=idx,its[idx]=ss,idx++;
    if(!sti.count(tt)) sti[tt]=idx,its[idx]=tt,idx++;
    for(int i=1;i<=n-1;i++)
    {
        string pla;
        int jiu;
        cin>>pla>>jiu; //新的地方与敌人数量
        //如果这个新地方先前没出现过 映射记录
        if(!sti.count(pla)) sti[pla]=idx,its[idx]=pla,idx++;
        ren[sti[pla]] = jiu;
    }
    for(int i=1;i<=m;i++)
    {
        string l,r;
        int dd;
        cin>>l>>r>>dd;
        //两个城镇的双向道路、距离
        //新地方没出现过 映射记录
        if(!sti.count(l)) sti[l]=idx,its[idx]=l,idx++;
        if(!sti.count(r)) sti[r]=idx,its[idx]=r,idx++;
        f[sti[l]][sti[r]] = min(f[sti[l]][sti[r]],dd);
        f[sti[r]][sti[l]] = f[sti[l]][sti[r]];
    }
    //迪杰斯特拉
    dij(sti[ss],sti[tt]); //起点终点字符串对应的下标
    int ed = sti[tt]; //终点字符串对应的下标
    stack s; //栈,把逆序的路线调成正序
    while(ed!=-1) s.push(its[ed]),ed=pre[ed]; //塞入逆序路径,-1表示前面没有城镇了
    ed = sti[tt]; //设置终点
    while(s.size()) //栈逆序输出逆序路线,就是正序路线了
    {
        cout<";
    }
    //路线数,最短距离,距离最短时杀敌数
    cout<

Python代码:无

L3-2 拼题A打卡奖励(30分)

思路:01背包问题,需要注意一点优化,就能卡常数通过了(有两个测试点是380+ms卡过的)。

提示:1.这道题的M最多是360*60*24,接近550000;但仔细观察,N*m_{i}最多可以到达600000,在本题的一些测试点不能起到优化作用,但有些题目可以缩小体积的枚举范围

           3.这道题的数组放到外面就会超时;放到main里面就能卡过去。不知道为什么。

C++代码:

#include
using namespace std;
typedef long long ll;
#define append push_back
#define print printf

int main()
{
    //数组放main里面可以优化时间
    int f[550005];
    int v[1005],w[1005];
    int n,m;
    scanf("%d%d",&n,&m);
    int su=0;
    int i,j;
    for(i=1;i<=n;++i) scanf("%d",&v[i]),su+=v[i]; //计算所有物品体积和
    for(i=1;i<=n;++i) scanf("%d",&w[i]);
    m=min(m,su); //总体积和所有物品的体积和取最小值 可能缩小体积枚举范围
    //逆序枚举01背包 一维优化
    for(i=1;i<=n;++i)
        for(j=m;j>=v[i];--j)
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d",f[m]);
    return 0;
}

Python代码:无

L3-3 可怜的简单题(30分)

原题链接:L3-030 可怜的简单题 - 团体程序设计天梯赛-练习集

思路:骗分法,通过assert测试点发现两个测试点n<=10,之后验证有一个测试点n==7(4分),还有一个测试点n==1(1分)。根据题目给的条件在PTA的编译器上(不会超时)做了一千万次模拟实验,模拟的数据接近正确答案。可惜这道题不是输出“精确到小数后n位”,而是用快速幂+乘法逆元的形式输出答案。

模拟了多轮发现n==1是序列的期望长度总是1,n==7的序列期望长度接近2.3166,后来根据直觉和数学分式计算得出n==7时的分数形式可能是139/60,结果提交,答案正确。

提示:1.PTA网站上的团体程序设计天梯赛-练习集的L3-030 可怜的简单题有陈越姥姥准备的彩蛋,可以看题目解析,但没有贴出正解代码。

           2.有人cout<<36;骗到了4分!所以如果时间充裕又发现无题可做,真的可以尝试从0枚举到50(当然有时间可以枚举更多)一个一个输出答案骗分。参考这位大佬的博客:2021团体程序设计天梯赛 L3-3 可怜的简单题 - Whiteying - 博客园

           2.C++的assert在天梯赛很有用,它的用法是:assert(条件);条件的写法和if里面的条件一模一样,例如assert(n<=15);如果assert里面的条件为假,PTA对应的测试点会立即返回“运行时错误”。这样你就可以确定这道题目的测试点范围、特征方便骗分;或者用在你认为非常坑的题目去测试有没有你猜想的坑点。Python的assert也一样,用法是:一行 assert n <= 15 不需要加任何符号。

           3.正解:[2021 CCCC天梯赛] 可怜的简单题 - 凌乱之风的博客

C++代码:

#include
using namespace std;
typedef long long ll;
#define append push_back
#define print printf
ll n,p;
ll lensum,shusum; //长度总和,数组个数
ll ksm(ll a,ll b,ll p) //快速幂 记得开long long
{
    ll res = 1;
    while(b)
    {
        if(b&1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}
ll gcd(ll a,ll b) //求gcd
{
    while(b!=0)
    {
        ll tmp = b;
        b = a % b;
        a = tmp;
    }
    return a;
}
void shiyan()
{
    int cishu = 0; //生成次数就是该次实验序列的长度
    int gc = 0; //初始设gcd=0
    while(true)
    {
        if(gc==1) break; //gcd为1终止添加
        ll cur = (rand())%n+1;
        if(gc==0) gc = cur; //特判第一个数
        else gc = gcd(gc,cur); //第二个数开始求gcd
        cishu ++; //序列长度增加1
    }
    //序列长度和数组长度的增加
    lensum += cishu;
    shusum += 1;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    srand(time(NULL));
    cin>>n>>p;
    //做一千万次实验,期望接近平均值
    // for(int i=1;i<=10000000;i++) shiyan();
    // cout<

Python代码:无

感谢您的观看!阅读这么久了,来一个赞~(旺柴)

你可能感兴趣的:(算法,c++,python)