前言:2023年的天梯赛难度是近年来最难的,L1有些难度、L2难度大增、L3近年最难。短短3小时内,该如何获得尽可能高的分?这里提供了一系列技巧,重点会加粗。
参考资料:2023年天梯赛的国家奖切线是250/220/175;如果采用近两年的新切线算法(即根据175分及以上的1:3:6人数比例颁奖),国家奖切线是226/193/175。
接下来直接上技巧与代码,全文共23386字,配合右边的目录阅读体验更佳~
如果这篇文章被自动设置为VIP可见或者付费,请尽快联系我。
L1-基础级(100分)
题目偏难,这一年挖坑的题变成2道了(参考L1-6和L1-7),很容易写错。
但L1部分基本上还是涵盖了近年来L1的所有考点:
简单输出、顺序语句、分支语句、循环语句、字符串处理、较复杂模拟。
建议学习set、unordered_set、map、unordered_map,对部分题目有显著效果。
L1必有至少一道题目挖坑。(例如审题、一些边界问题、暴力做法超时等)
L1的绝大多数题目,用任何语言写出正解不会超时。
C++选手的优秀手速(自己可以尝试训练到这个标准,可以给L2和L3多出很多做题时间):
获奖过天梯赛的选手:达到95分:30分钟。达到100分:40分钟。
未获奖过天梯赛的选手,在50-60分钟内得到90-95分就很不错;熟练后可按上述要求练习手速。
L1-1 最好的文档(5分)
原题链接:L1-089 最好的文档 - 团体程序设计天梯赛-练习集
思路:直接输出字符串即可。
提示:这道题可以存你的模板/头文件,方便直接复制。
C++代码:L1-089 最好的文档_好代码本身就是最好的文档-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 #快写,不会自带换行,要手动换行
#上面是模板,可以复制到其他题目
print("Good code is its own best documentation.")
#C++模板,记得在PTA上的Python3语言环境同时按下Ctrl+/,解除注释,然后切换到C++语言
#天梯赛可以用不同的语言交题目(例如一道题用Python3,另一道题用C++)
# #include<bits/stdc++.h>
# 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分)
原题链接:L1-090 什么是机器学习 - 团体程序设计天梯赛-练习集
思路:直接分行输出4种结果即可。
提示:无。
C++代码:L1-090 什么是机器学习-CSDN博客
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,b = map(int,input().split()) #用map来将分割后字符串的存到对应的数字里面
print(a+b-16)
print(a+b-3)
print(a+b-1)
print(a+b)
L1-3 程序员买包子(10分)
原题链接:L1-091 程序员买包子 - 团体程序设计天梯赛-练习集
思路:用if-else语句判断每个情况,题目的输出格式已经提示了。
提示:无。
C++代码:L1-091 程序员买包子 分数 10 - Frodnx - 博客园
Python代码:
from heapq import heappush,heappop
from bisect import bisect,bisect_left
from math import sqrt,ceil,floor
from collections import deque
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write
nn,x,mm,kk = input().split()
n,m,k = int(nn),int(mm),int(kk)
#输出字符串用%s,和C语言一样
if k == n:
print("mei you mai %s de"%x)
elif k == m:
print("kan dao le mai %s de"%x)
else:
print("wang le zhao mai %s de"%x)
L1-4 进化论(10分)
原题链接:L1-092 进化论 - 团体程序设计天梯赛-练习集
思路:用if-else语句判断每个情况,题目的输出格式已经提示了。
提示:无。
C++代码:pta L1-092 进化论_l1-092python-CSDN博客
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
n=int(input())
for i in range(n):
#直接输入输出就行
a,b,c = map(int,input().split())
if c == a*b:
print("Lv Yan")
elif c == a+b:
print("Tu Dou")
else:
print("zhe du shi sha ya!")
L1-5 猜帽子游戏(15分)
原题链接:L1-093 猜帽子游戏 - 团体程序设计天梯赛-练习集
思路:输入正确答案后,用for循环读入每组“猜”的情况,之后根据情况输出Da Jiang!!!
或者Ai Ya
,建议直接从题目复制,自己手打容易打错。
提示:1.只要有一个不正确,就是Ai Ya
;
2.如果所有人都弃权(即全0),也是Ai Ya
;
3.只要有人猜对,且其他人弃权或无人猜错,就是Da Jiang!!!
。
C++代码:PTA L1-093 猜帽子游戏 (15 分)-CSDN博客
Python代码:
from heapq import heappush,heappop
from bisect import bisect,bisect_left
from math import sqrt,ceil,floor
from collections import deque
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline #快读
prin = sys.stdout.write #快写
#inpu是快读,不是打错了,后面题目同理
n = int(inpu())
a = list(map(int,inpu().split())) #根据读入的行转为整型列表(类似C++的数组)
m = int(inpu())
for i in range(m):
b = list(map(int,inpu().split()))
dui = 1 #假设所有人猜对
f0 = 0 #非0,就是是否有人猜
for j in range(n):
if b[j] != 0:
f0 = 1 #有人猜,排除所有人弃权的情况
if b[j] != a[j]: #这里的条件嵌套于 b[j] != 0 内,即这个人不是弃权的
dui = 0
if dui == 1 and f0 == 1: #如果有人没弃权,猜的人全猜对,就输出大奖
print("Da Jiang!!!")
else:
print("Ai Ya")
L1-6 剪切粘贴 (15分)
原题链接:L1-094 剪切粘贴 - 团体程序设计天梯赛-练习集
思路:对于每个操作,有4个参数,分离字符串的起始下标,分离字符串的结束下标,要查找的两个字符串。如果能找到一种两个字符串相邻(不能有重叠)的情况,就把分离的字符串塞入第一次出现的相邻字符串之间的那个位置;否则塞入末尾。
提示:1.题目每组操作提供的下标从1开始,记得转从0开始的下标,或者进行其他特殊处理。
2.必须先把要复制的字符串分离出来再寻找对应位置;假设有一组操作:1 3 ab c,而原字符串是abcdeabc,那么先把前面的abc分离出来,得到字符串deabc,找到的第一个位置是de后面的abc,之后字符串变成deababcc。
3.找出来的位置不能重叠。假设有一组操作:9 10 hav ave,原字符串是 Ihaveanapple,此时不应该记录位置(因为have的hav和ave是重叠的)。正确的做法是找不到相邻的字符串,把分离的字符串塞入字符串末尾。
4.如果有选手使用两个字符串分开找的方法,记住一定要找到第一次出现的相邻字符串的那个位置。假设有一组操作:1 2 e f,原字符串abessfssfsesefsesfefe,则设计的算法要将分离的字符串放入第一次出现的字符串(即上述字符串加粗的位置)的中间。输入样例的最后一组操作就考虑了这个情况。
5.C++选手请灵活使用substr和find的函数,解决L1的字符串问题非常有用!可以参考下方的文章。如果学有余力,可以学习这篇文章的其他STL字符串操作函数。Python选手也尝试掌握find方法、replace方法和切片的熟练应用。注意Python的标准str类型不能直接修改字符串的一部分(哪怕只修改一个字母也不行),只能重新切片并组合。
C++代码:PTA L1-094 剪切粘贴 (超简版——灵活运用string模板库里的函数(也就练习了半坤年))_剪切粘贴pta-CSDN博客
Python代码:
from heapq import heappush,heappop
from bisect import bisect,bisect_left
from math import sqrt,ceil,floor
from collections import deque
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write
a = input()
n = int(input())
for i in range(n):
ll,rr,aa,bb = input().split() #不能直接用map转int,输入的类型不完全是int
l,r = int(ll),int(rr) #单独转int操作
#python的切片是左闭右开区间,截取了原字符串的[l-1,r-1]部分,下标自动转化为从0开始
tmp = a[l-1:r]
a = a[:l-1] + a[r:] #原字符串删去被截取的字符串
#print(a) #调试语句 检查中间a的变化情况
#采取分开找相邻字符串的策略 p1存储相邻字符串左边的末尾+1个位置
#p2存储相邻字符串右边的开头位置 这样进行匹配时中间的下标就是一样的
#例如对"there"查找the和re,p1找到"the"存储下标3,p2找到"re"存储下标3
p1 = []
p2 = []
#基于切片的查找操作 左相邻字符串记录末尾+1(即i+len(aa))下标,右相邻字符串记录开头下标
for i in range(len(a)):
if a[i:i+len(aa)] == aa:
p1.append(i+len(aa))
for i in range(len(a)):
if a[i:i+len(bb)] == bb:
p2.append(i)
pos = -1 #假设没找到
fff = 0 #找到第一个的标志,为1直接break
#O(n^2)暴力枚举相邻下标的过程
for i in p1: #相当于C++的 for(auto i:p1)
if fff == 1:
break
for j in p2:
if i == j: #下标相等
fff = 1 #找到第一个了 不需要再找
pos = i #记录要插入的那个下标
break
if pos == -1:
a = a + tmp #没找到插入末尾
else:
a = a[:pos] + tmp + a[pos:]
print(a)
L1-7 分寝室 (20分)
原题链接:L1-095 分寝室 - 团体程序设计天梯赛-练习集
思路:n0、n1、n是女生人数、男生人数、宿舍数。可以开一个vector表示一间宿舍可能住下的人数,用for循环枚举i从1到(每种性别总人数-1)的宿舍数量与对应的人数,就是 n0/i 或 n1/i(注意这个是人数),如果能被整除就塞入对应性别的vector。
为什么只能枚举到每种性别总人数-1?因为题目明确表示不能单人单间。之后用两层for循环暴力枚举男生和女生宿舍数的总和是否为n,如果为n取宿舍数量的差最小的那一对答案。两层for循环暴力枚举不会超时,因为据资料统计:10的5次方含有最多个约数的数,他的约数也只有128个。
提示:1.vector存的是宿舍当前住的人数。
2.按照上述的思路,判断刚好凑满n间宿舍的代码是:
//C++
if (n0/i + n1/j == n)
//Python 除号要用//才能向下取整
if n0 // i + n1 // j == n:
3.最后输出的是宿舍数,即for循环枚举的i,j得来的 n0/i 与 n1/j,更新答案赋值用这一个值;但判断并更新最小差的是人数差,即 i , j。记得用abs(i-j)更新人数差,因为枚举的j可能比i更小。
4.记得特判No Solution的情况。
5.下方文章的代码与这篇文章的思路大致一样,他的方法不需要预处理,代码稍微简单一些,可以做一个参考。
C++代码:PTA L1-095 分寝室(理解题意轻松WA)_学校新建了宿舍楼,共有 n 间寝室。等待分配的学生中,有女生 n 0 位、男生 n 1 位-CSDN博客
Python代码:
from heapq import heappush,heappop
from bisect import bisect,bisect_left
from math import sqrt,ceil,floor
from collections import deque
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write
n0,n1,n = map(int,input().split())
#其实不用set直接用列表也可以,那这样add需要改成append
#注意男女存储的是人数
na = set()
nv = set()
#python的for循环左闭右开,不会循环到n0和n1
for i in range(1,n0):
if n0 % i == 0:
na.add(n0 // i) #枚举所有可能出现的人数,下同
for i in range(1,n1):
if n1 % i == 0:
nv.add(n1 // i)
cha = int(1e9) #取一个足够大的数初始化最小的人数差
naa,nvv = -1,-1 #-1,-1表示无解
for i in na:
for j in nv:
if n0 // i + n1 // j == n:
if (abs(i-j)) <= cha: #一定要开abs,人数差不会是负数
cha = abs(i-j)
naa,nvv = n0 // i,n1 // j #注意结果更新的是宿舍数
if naa == -1 and nvv == -1:
print("No Solution")
else:
print(naa,nvv)
L1-8 谁管谁叫爹(20分)
原题链接:L1-096 谁管谁叫爹 - 团体程序设计天梯赛-练习集
思路:这题算是比较简单的L1-8,直接求与
的数位和,赋值到
与
中,最后判断两个条件满足其一、都满足、都不满足的情况即可。
提示:1.这道题比L1-6和L1-7都要简单,所以如果这两道题的边界条件没有调好,可以先放下L1-6和L1-7的一小部分测试点做L1-8,等回头再补这两道题;
2.不能直接让与
除以自身求出数位和,这样出现相同的情况就没法比较原先的数谁大了,可以创建一个临时变量tmp,令tmp等于
与
,之后求
与
。
C++代码:PTA-L1-096 谁管谁叫爹(20分)_pta谁管谁叫爹-CSDN博客
Python代码:
from heapq import heappush,heappop
from bisect import bisect,bisect_left
from math import sqrt,ceil,floor
from collections import deque
import sys
import time
sys.setrecursionlimit(1000010)
sys.set_int_max_str_digits(1000010)
inpu = sys.stdin.readline
prin = sys.stdout.write
#据说python的for循环比while快 又不想让嵌套循环(如果有)的i冲突 可以尝试用无用变量名
n = int(input())
for AAA in range(n):
na,nb = map(int,input().split())
sa,sb = 0,0
tmpa,tmpb = na,nb #tmp作为临时变量
#计算数位和
while tmpa > 0:
sa += tmpa%10
tmpa //= 10
while tmpb > 0:
sb += tmpb%10
tmpb //= 10
#判断几种情况
if na%sb==0 and nb%sa!=0:
print("A")
elif nb%sa==0 and na%sb!=0:
print("B")
elif na>nb: #都满足或都不满足 判断原先数字大小
print("A")
else:
print("B")
L2-进阶级(100分)
2023年的L2-1和L2-4是简单题,L2-2中等题(容易超时);L2-3难题,需要一定的构造技巧。
L2部分基本上还是涵盖了近年来L2的考点(基本上每年都是这几个考点):
STL容器、复杂模拟、数据结构(树考的偏多)、搜索(或图论),对应题号1-4。
建议多练PTA上的团体程序设计天梯赛的练习集的L2题目,熟练之后会好做很多。
L2的L3一些题目,用其他语言写正解可能会超时,所以最好用C++。
这里给出L2和L3可以用Python的判断时间复杂度方法:
时间复杂度总和少于的,除了频繁对列表进行操作(例如for循环、修改值等),放心使用。
除了图论外,给Python延长了时限的题目,放心使用。
C++选手的优秀手速(自己可以尝试训练到这个标准,可以给L3多出很多做题时间):
参加过天梯赛的选手:达到85分:50分钟。达到100分:70分钟。
第一次参加天梯赛的选手,如果L1在1小时内获得了90分以上,在剩余2小时内在L2和L3得到85分及以上就得到了国家三等奖;熟练后可按上述要求练习手速。
L2-1 堆宝塔(25分)
原题链接:L2-045 堆宝塔 - 团体程序设计天梯赛-练习集
思路:这题不难,按照题目的描述模拟就能出结果了。
提示:1.注意“然后把 B 柱上所有比 C 大的彩虹圈逐一取下放到 A 柱上,最后把 C 也放到 A 柱上。”这一个条件,不是把B柱的所有彩虹圈放到A柱上。
2.注意对最后一个彩虹圈操作后要判断A塔和B塔有没有彩虹圈,如果有要额外计算。
C++代码:L2-045 堆宝塔 - YuKiCheng - 博客园
Python代码:
from heapq import heappush,heappop
from bisect import bisect,bisect_left
from math import sqrt,ceil,floor
from collections import deque
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())
d = list(map(int,input().split())) #整行读取数字生成列表
ge,ceng = 0,0 #个数和最高多少层
#A塔和B塔
a = []
b = []
a.append(d[0]) #放置第一个彩虹圈
for i in range(1,len(d)):
c = d[i]
if c < a[-1]: #c比a塔顶小 -1的下标意思是列表的最后一个数,-2 -3 ...同理
a.append(c)
elif len(b) == 0 or c > b[-1]: #b为空或c比b塔顶大
b.append(c)
#print(b) #调试用的语句
else: #将A摘下来做成品
ge += 1
ce = 0
while len(a) != 0:
#print(a) #调试用的语句
a.pop()
ce += 1 #获得1个宝塔
ceng = max(ce,ceng) #取最高层数
while len(b) != 0 and b[-1] > c: #然后把c大的摘下来套到a
a.append(b.pop())
a.append(c)
#最后特判a塔和b塔有彩虹圈的情况
if len(a) != 0:
ge += 1
ce = 0
while len(a) != 0:
#print(a)
a.pop()
ce += 1
ceng = max(ce,ceng)
if len(b) != 0:
ge += 1
ce = 0
while len(b) != 0:
#print(b)
b.pop()
ce += 1
ceng = max(ce,ceng)
print(ge,ceng)
L2-2 天梯赛的赛场安排(25分)
原题链接:L2-046 天梯赛的赛场安排 - 团体程序设计天梯赛-练习集
思路:这道题有一个坑点,属于中等难度,理解题意做的就会清晰了。因为题目说明一个学校剩余的人员一定会集中在一个赛场,所以监考人数就是这所学校占用的赛场数。读者可以拿出笔来算一算。所以:可以边读入边判断,直接输出大学名称和占用赛场数(如果有剩余的人数+1)。剩余的人数在后面简称“余数”。之后如果出现余数,则将这个余数塞入vector中。
之后这道题目的坑点来了:按照尚未安排赛场的队员人数从大到小的顺序!也就说明必须要先将余数排序才能模拟题目给出的步骤。之后按照题目步骤暴力模拟即可(9ms,非常快)。注意Python会超时。
提示:1.注意要先将余数排序才能执行题目的步骤。
2.有些方法要特判余数的vector是不是空的。
3.这道题用Python会超时。
C++代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define append push_back //python用习惯 避免写错用define替换了
#define print printf
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,m;
int sai = 0; //完整赛场数
cin>>n>>m;
vector<int> s; //存储余数
vector<int> q; //存储余数组成的赛场数量
for(int i=1;i<=n;i++)
{
string a;
int b;
cin>>a>>b;
int tmp = b/m;
sai += tmp; //凑出tmp个完整的赛场
int yu = b%m; //剩余的人群(余数)
if(yu!=0) tmp += 1; //特判余数,加一个监考
cout<<a<<' '<<tmp<<'\n'; //直接输出
if(yu != 0)
{
s.append(yu); //塞入存储余数的vector
}
}
//sort默认从小到大排序,greater的作用是从大到小排序
//不会的选手也可以直接排序后reverse这个vector 或者直接暴力swap
sort(s.begin(),s.end(),greater<int>());
if(s.size()) //特判没有余数的情况 有余数才能分配赛场
{
q.append(s[0]); //最多的余数先安排一个赛场 要特判s是不是空的
for(int i=1;i<s.size();i++)
{
bool fff = 0; //判断是否找到赛场
for(int j=0;j<q.size();j++)
{
//如果能找到塞进去不超人数的赛场就进去
if(s[i]+q[j]<=m)
{
q[j] += s[i];
fff = 1;
break;
}
}
if (!fff) q.append(s[i]); //找不到赛场就新建一个 使用append原因见上
}
}
cout<<sai+q.size(); //输出完整赛场数和由余数组成的赛场数的总和
}
Python代码:无
L2-3 锦标赛(25分)
原题链接:L2-047 锦标赛 - 团体程序设计天梯赛-练习集
思路:这道题需要构造,先建立一个55万左右大小的数组(因为再加上最后一层节点的左右孩子,总共
个节点),再将这个数组初始化为-1。
之后对每一层选手进行输入,第i层的下标范围是到
。这样就自然形成一颗完全二叉树了。对最后一层的选手设立左孩子和右孩子(其实就是最后一层的再下一层)。用数组下标存,假设下标为u,则左孩子的下标是2*u,右孩子的下标是2*u+1,并让右孩子的值等于自己。
至于最后一行的节点,可以用下标0存储。
之后再从最后一层自下到上将每一个节点执行pushdown操作。这个操作的步骤是:
1.获取当前的节点的值(下文称“源节点的值”)和指针(在这里指的是下标)。
2.对当前节点的左孩子、右孩子搜索枚举。如果左孩子(右孩子)的值小于等于源节点的值,则尝试把节点指针移动到左孩子(或者右孩子),不需要回溯。注意:如果左孩子和右孩子都满足条件,则两种情况都执行一遍。如果指针没到底(就是最后一层的再下一层,即最后一层某个节点的左孩子和右孩子),重复执行这一步。
3.如果指针到底了,检查值:如果是-1(表示这个位置还没有放置任何数字)而且这个源节点的值能打败右孩子(也就是最底层的那个选手),则将这个数字放入这个位置,之后终止一切搜索的操作;如果不能打败或者不是-1就不用再往下搜索了,因为已经到底。
然后再对0号节点从1号开始执行pushdown操作。操作完毕后,有两种情况输出No Solution:
1.0号节点的值小于1号节点的值,此时0号不可能是冠军。
2.最后一层的再下一层仍有-1(即未塞满)。
提示:1.直接输出No Solution可以骗分,得到2分。
2.如果当前节点的值已经放置在最后一层的再下一层,则剪枝。
C++代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define append push_back
#define print printf
int a[600005];
int bas;
bool f = 0;
void insert(int x,int po) //pushdown操作 x为源节点的值 po为当前位置指针(下标)
{
if(f) return ; //这个节点已经塞入成功了
if(po>=bas) //从bas开始的下标就是最后一层的再下一层节点了
{
//如果这个位置还没塞入 而且 x能打败这位选手
if(a[po] == -1 && x >= a[po+1])
{
a[po] = x; //位置塞入值
f = 1; //塞入成功
}
return ;
}
int l = po * 2;
int r = po * 2 + 1;
//转移指针到左孩子、右孩子
if(a[l] <= x) insert(x,l);
if(a[r] <= x) insert(x,r);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(a,-1,sizeof(a));
int n;
cin>>n;
bas = pow(2,n); //所有输入的下标范围是0到pow(2,n)-1 从bas开始就是最后一层的再下一层
//对每一层的节点进行输入
for(int i=n;i>=1;i--) //第i层的所有节点,i从最后一层开始
for(int j=pow(2,i-1);j<pow(2,i);j++)
cin>>a[j];
cin>>a[0]; //输入冠军
for(int i=bas/2;i<bas;i++) a[i*2+1] = a[i]; //右孩子的值为自己
//pushdown操作 f表示是否塞入成功
for(int i=bas/2-1;i>=1;i--) f=0,insert(a[i],i);
f=0; //塞入0号节点 初始化还没塞入成功
//swap(a[0],a[1]);
if(a[0]<a[1]) //0号节点不是冠军
{
cout<<"No Solution";
return 0;
}
insert(a[0],1); //塞入0号节点
for(int i=bas;i<2*bas;i++)
{
if(a[i]==-1) //还有位置没被塞入
{
cout<<"No Solution";
return 0; // return 0;表示后面的都不用执行了
}
}
for(int i=bas;i<2*bas;i++) //输出最后一层的再下一层的节点
{
if(i!=bas) cout<<' ';
cout<<a[i];
}
return 0;
}
//之前写了个贪心算法造这组数据不能通过 上述算法能通过这组数据
// 3
// 1 1 1 1
// 2 4
// 3
// 4
Python代码:无
L2-4 寻宝图(25分)
原题链接:L2-048 寻宝图 - 团体程序设计天梯赛-练习集
思路:这道题比L2-2和L2-3简单,用DFS或者BFS做连通块搜索。如果搜到至少1个大于1的数字,就认为这个岛屿上有宝藏。要注意暴力的方法会内存超限,但好在总块数在10万之内。于是可以用一些容器存储。
提示:1.一个岛屿上有多个宝藏时,有宝藏的岛屿只能被算作一个。
2.用vector或者string存都可以。
3.注意vis数组或者st数组的存储方式,可以是上述两种容器之一,也可以是其他存储方法。
4.C++题解的作者使用了DFS,我的题解使用了BFS。
C++代码:【团体程序设计天梯赛】L2-048 寻宝图-CSDN博客
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
n,m = map(int,input().split())
a = []
vis = [[0] * (m+1) for i in range(n+1)] #python可以动态开数组
#dx dy数组控制方向
dx = [1,0,-1,0]
dy = [0,1,0,-1]
def bfs(x,y):
f = 0
#x y单独构成两个队列 也可以用元组合并成一个
qx = deque()
qy = deque()
qx.append(x)
qy.append(y)
while len(qx) != 0:
cx=qx.popleft()
cy=qy.popleft()
if vis[cx][cy] == 1:
continue
vis[cx][cy] = 1
if a[cx][cy] != '0' and a[cx][cy] != '1': #发现宝藏了
f = 1
for i in range(4):
#加入偏移量
fx,fy = cx+dx[i],cy+dy[i]
#判断这个点是否是陆地 而且在范围内
if fx >= 0 and fy >= 0 and fx < n and fy < m and a[fx][fy] != '0':
qx.append(fx)
qy.append(fy)
return f
ans,bao = 0,0 #岛屿数量,有宝藏的岛屿数量
for i in range(n):
a.append(input()) #直接当字符串塞入a列表
#print(a) #调试语句
for i in range(n):
for j in range(m):
#一次搜索连通块时 所有的vis(或者st)都会标记为1 所以搜索次数就是连通块个数
if vis[i][j] == 0 and a[i][j] != '0':
ans += 1
#print(i,j,a[i][j])
if bfs(i,j) == 1:
bao += 1
print(ans,bao)
L3-登顶级(90分)
2023年的L3没有一个是简单的题目。L3-1是图论+大模拟;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-034 超能力者大赛 - 团体程序设计天梯赛-练习集
思路:第一步与扩充说明的描述是:“从所有超能力者(包括联盟)中找出一个与自己能力值最接近、同时自己能够击败的对手,用最短时间去到对方的城市,在到达后第二天击败之,有可能遇到多个符合条件的对手,并列情况下优先选最近的;距离也并列的情况下选途径城市最少的;再有并列就选城市编号最小的。”就可以想到多源最短路。提到多源最短路,就可以想到Floyd算法。维护两个邻接矩阵,第一个邻接矩阵f维护最短距离,第二个邻接矩阵f维护途径的最短城市数。设定好距离后跑floyd最短路。
题目先设定超能力者的初始地点和能力值,记住:0号超能力者是自己,所以先设定自己的能力值后,下标再从1开始输入。推荐用priority_queue的小根堆形式维护敌人的能力值。原因是:假设使用vector维护,某个城市有100个能力值为1的能力者,又有1个能力值为200的能力者,你的能力值为1。击败一个超能力者后,剩下99位能力者合体,插入到200的后面,不符合最小的条件。那么这样,vector在每次击杀完敌人的时候都要sort,可能会超时(我没试过)。
输入完超能力者和边后就可以Floyd了。然后就是大模拟。我将模拟分为3个步骤:寻路,走路,杀敌。我们来拆解这3个步骤:
1.寻路:根据题目描述找到下一个地点(见思路加粗部分)。设定最终地点的编号初始值是0x3f3f3f3f。先特判所有城市的敌人是否都被击杀了(在这里我用优先队列q[i].size()==0),如果是,返回一个特定值,输出WIN,游戏结束。接下来就是正常的步骤。如果编号到了结束依然是0x3f3f3f3f,则所有人的能力值都比你大,返回特定值,输出Lose,游戏结束。否则执行第2步。注意寻找最接近的能力值需要把每个优先队列都拆开(如果上述使用vector存敌人直接遍历然后erase),即pop到一个临时的队列,之后塞回去;这样才能找到与自己能力最接近的能力者(而不是能力最小的)。见样例1的4号城市,初始堆顶是8,要拆开优先队列才能找到“10”。
2.走路:每走一步,day++,dist--;如果day到了游戏结束那天,输出Game Over,游戏结束;否则直到dist走完到下一个城市。注意:下一个城市可以是自己本身(见样例2),如果不使自己本身,就输出Move。
3.杀敌:先拆开这个城市的优先队列(或者vector找到对手并erase),击杀对手。如果天数到了,判输赢和Game Over;增加自己的能力值后,这个城市所有能力小于等于你的超能力者都合并成一个联盟,视为一个超能力者。之后把这个联盟塞入优先队列。一直战斗,直到打不过堆顶,或者所有人已经被你击杀,或者天数到了,判输赢和Game Over。
一直重复这3个步骤,直到判断输赢或者游戏结束。
提示:1.这道题非常考验调BUG的能力与做题的心态,我花了接近一个小时获得25分,应该是一些边界的问题。有些情况下会在游戏结束前输出多个WIN、Lose、Game Over,此时设一个bool变量判断游戏终止的语句输出没有,输出过了就不再输出。
2.下标都是从0开始的,道路是双向的。
3.如果下一个目的地是当前城市,不能输出Move语句。
4.先合并能力值,再执行“敌人合体成联盟”的操作。
5.到了最后一天,先判断输赢,再判断Game Over。
6.即使这道题的Python时限开到900ms,Python代码在Floyd结束后就超时了。
7.目前在网络上没有找到公开的正解。
C++代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define append push_back
#define print printf
int n,m,me,d;
int f[205][205]; //存最短路
int f2[205][205]; //存最短途径点数
int mypos,mynengli; //你当前所处的城市编号和能力值
int day,jiejin; //day是天数 jiejin是最接近你能力的超能力者的能力值
priority_queue<int,vector<int>,greater<int> > q[205]; //每个城市的超能力者存在优先队列
bool shuchu = 0;
void floyd()
{
for(int k=0;k<=202;k++)
for(int i=0;i<=202;i++)
for(int j=0;j<=202;j++)
{
if(f[i][j] > f[i][k]+f[k][j]) //距离为第一关键字更新
{
f[i][j] = f[i][k] + f[k][j];
f2[i][j] = f2[i][k] + f2[k][j];
} //距离一样,经过条数为第二关键字更新
else if(f[i][j] == f[i][k]+f[k][j] && f2[i][j] > f2[i][k]+f2[k][j])
{
f2[i][j] = f2[i][k] + f2[k][j];
}
}
}
int select() //选点
{
int mincha = 0x3f3f3f3f; //最小能力差
int dist = 0x3f3f3f3f; //距离
int tujing = 0x3f3f3f3f; //途径点数
int bian = 0x3f3f3f3f; //下一个要到的城市编号
bool gg = 1; //所有敌人都被击杀的标记
for(int i=0;i<m;i++) if(q[i].size()!=0) gg = 0; //有城市的敌人还没被击杀
if(gg) return -1; //所有敌人被击杀 返回胜利
for(int i=0;i<m;i++) //顺序枚举保证取到编号最小
{
if(q[i].size() == 0) continue; //没人了
if(mynengli < q[i].top()) continue; //比你强
//拆优先队列找最接近的操作
queue<int> tmp;
int cha = mynengli - q[i].top();
//能力小于等于你的人 一个个找
while(q[i].size() && mynengli >= q[i].top())
{
tmp.push(q[i].top());
cha = min(cha,mynengli - q[i].top()); //一个个更新
q[i].pop();
if(cha == 0) break; //刚好遇到与你一样的既不用继续找了
}
//保证将优先队列复原成原样 因为pop了最小 push的也会是最小
while(tmp.size()) q[i].push(tmp.front()),tmp.pop();
if(cha < mincha) //能力差值为第一关键字
{
jiejin = mynengli - cha;
mincha = cha;
dist = f[mypos][i];
tujing = f2[mypos][i];
bian = i;
}
else if(cha == mincha && f[mypos][i] < dist) //距离为第二关键字
{
dist = f[mypos][i];
tujing = f2[mypos][i];
bian = i;
}//途径个数为第三关键字 此时要严格小于号 保证取到的编号最小
else if(cha == mincha && f[mypos][i] == dist && f2[mypos][i] < tujing)
{
tujing = f2[mypos][i];
bian = i;
}
}
//cout<<mincha<<' '<<dist<<' '<<tujing<<' '<<bian<<'\n'; //调试语句
return bian; //如果bian还是0x3f3f3f3f 就说明没有能力<=你 你输了
}
void zhandou(int u) //战斗
{
//先击杀那位超能力者
queue<int> tmp;
//拆队列操作
while(q[u].size() && q[u].top() != jiejin)
{
tmp.push(q[u].top());
q[u].pop();
}
int p = q[u].top();
mynengli += p; //先加能力值
print("Get %d at %d on day %d.\n",q[u].top(),mypos,day);
//将那位接近能力者取出来,表示不在队列中已经被击杀
q[u].pop();
//复原优先队列操作
while(tmp.size()) q[u].push(tmp.front()),tmp.pop();
day += 1; //击杀一个敌人day++
//特判时间到,下同
if(day > d && !shuchu) //shuchu表示已经输出语句了 这点在提示有说
{
shuchu = 1; //表示已经输出结束条件
int nextpos = select(); //选城市 返回WIN Lose 或正常编号(就是Game Over)
if(nextpos == -1) //所有城市优先队列为空 没有敌人了
{
print("WIN on day %d with %d!\n",min(d,day),mynengli);
}
else if(nextpos == 0x3f3f3f3f) //都比你大
{
print("Lose on day %d with %d.\n",min(d,day),mynengli);
}//还能到下一个城市 说明还有<=你的敌人
else print("Game over with %d.\n",mynengli);
return ;
}
//求<=你能力值的人的总和
int su = 0;
while(q[u].size() && mynengli >= q[u].top()) su+=q[u].top(),q[u].pop();
if(su != 0) q[u].push(su); //特判没有这种敌人,防止凭空出现一个能力值为0的敌人
//继续击杀操作,流程和上面基本是一样的,就不做注释了
while (q[u].size() && mynengli >= q[u].top())
{
p = q[u].top();
mynengli += p;
print("Get %d at %d on day %d.\n",q[u].top(),mypos,day);
q[u].pop();
day += 1;
if(day > d && !shuchu)
{
shuchu = 1;
int nextpos = select();
if(nextpos == -1)
{
print("WIN on day %d with %d!\n",min(d,day),mynengli);
}
else if(nextpos == 0x3f3f3f3f)
{
print("Lose on day %d with %d.\n",min(d,day),mynengli);
}
else print("Game over with %d.\n",mynengli);
return ;
}
su = 0;
while(q[u].size() && mynengli >= q[u].top()) su+=q[u].top(),q[u].pop();
if(su != 0) q[u].push(su);
}
return ;
}
int main()
{
memset(f,0x3f,sizeof(f));//初始化距离无穷大
//用了printf,不能用关同步流加速cin和cout操作
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
cin>>n>>m>>me>>d;
for(int i=0;i<=202;i++) f[i][i] = 0;//同一个城市的距离是0
//先输入自己的初始城市和初始能力值
cin>>mypos>>mynengli;
for(int i=2;i<=n;i++) //少了自己的所有能力者总数
{
//敌人所在城市与能力值
int cpos,cnengli;
cin>>cpos>>cnengli;
q[cpos].push(cnengli); //插入优先队列维护最小值
}
for(int i=1;i<=me;i++)
{
int l,r,di;
cin>>l>>r>>di; //输入两个城市与距离 题目说保证每条边只出现1次
f[l][r] = di;//双向距离
f[r][l] = di;//同上
f2[l][r] = 1;//途径点个数
f2[r][l] = 1;//同上
}
floyd(); //多源最短路
day = 1; //第1天开始
while(day <= d)
{
int nextpos = select();
//判断输赢逻辑参见战斗函数
if(nextpos == -1)
{
print("WIN on day %d with %d!\n",min(d,day),mynengli);
break;
}
if(nextpos == 0x3f3f3f3f)
{
print("Lose on day %d with %d.\n",min(d,day),mynengli);
break;
}
if(day == d+1)
{
print("Game over with %d.\n",mynengli);
break;
}
int dist = f[mypos][nextpos];
bool jieshu = 0;
while(dist > 0)
{
day += 1;
if(day > d)
{
jieshu = 1; //天数超过,表示结束
nextpos = select(); //先判断输赢
if(nextpos == -1)
{
print("WIN on day %d with %d!\n",day,mynengli);
}
else if(nextpos == 0x3f3f3f3f)
{
print("Lose on day %d with %d.\n",day,mynengli);
}
else print("Game over with %d.\n",mynengli);
break;
}
dist -= 1;
}
if(jieshu) break; //天数到了,游戏结束,不能去下一个城市
//没有去其他城市时不能输出这句话
if(mypos != nextpos) print("Move from %d to %d.\n",mypos,nextpos);
mypos = nextpos; //移动到下一个城市(可以是自己)
zhandou(mypos); //在下一个城市战斗
//break;
}
}
Python代码:无
L3-2 完美树(30分)
原题链接:L3-035 完美树 - 团体程序设计天梯赛-练习集
思路:骗分法,通过assert测试点可以发现前3个测试点的n<=15(后3个测试点n=100000),总价值17分。于是可以用二进制枚举所有点,该点所在的二进制位是0为白,1为黑。对所有种情况进行枚举,如果该树是完美树,判断所有点的初始颜色和二进制位的颜色是否一致,不一致就加上染色代价。之后在
种情况中的合法情况中取最小的代价即可。时间复杂度
。
提示:1.据说当年这道题有人输出20得到15分。我尝试了一下:前3个测试点,一个答案是20;一个答案是0;还有一个答案是权值最小的那个节点,共17分。
2.C++的assert在天梯赛很有用,它的用法是:assert(条件);条件的写法和if里面的条件一模一样,例如assert(n<=15);如果assert里面的条件为假,PTA对应的测试点会立即返回“运行时错误”。这样你就可以确定这道题目的测试点范围、特征方便骗分;或者用在你认为非常坑的题目去测试有没有你猜想的坑点。Python的assert也一样,用法是:一行 assert n <= 15 不需要加任何符号。
3.正解:2023 GPLT 天梯赛 L3-035 完美树 —— 树形DP,状态机,贪心-CSDN博客
C++代码:
//二进制枚举骗分法
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//color表示原先的颜色 dj是代价 col是二进制枚举的染色
ll color[100005],dj[100005],col[100005];
//储存节点的儿子
vector<int> to[100005];
ll mindj = (ll)(1e18); //最小代价
int n;
bool f = 1; //表示是完美树
pair<int,int> dfs(int u) //统计子树黑白节点树
{
if(!f) return {0,0}; //不符合 不用再搜索了
int bai=0;
int hei=0;
if(col[u]) hei ++;
else bai ++;
for(auto i:to[u])
{
pair<int,int> res = dfs(i);
bai += res.first;
hei += res.second;
}
if(abs(bai-hei)>1) f=0; //不符合完美树条件
return {bai,hei}; //要同时返回2个值 就用pair
}
void pd(int u)
{
f = 1; //假定这个树是完美树
for(int i=1;i<=n;i++)
col[i] = (u>>(i-1)) & 1; //i是从1开始的,二进制是从0次方开始的 所以要i-1
dfs(1);
if(f) //如果是完美树 更新最小的代价
{
ll su = 0;
for(int i=1;i<=n;i++)
if(col[i] != color[i]) su += dj[i];
mindj = min(mindj,su);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
//每一行前3个是颜色 代价 孩子个数
for(int i=1;i<=n;i++)
{
ll c,p,kk;
cin>>c>>p>>kk;
color[i] = c;
dj[i] = p;
for(int j=1;j<=kk;j++) //输入所有孩子
{
int x;
cin>>x;
to[i].push_back(x);
}
}
//二进制枚举 n=100000会爆int 不过即使将n=100000换成n=10 也骗不了分
for(int i=0;i<(1<<n);i++)
pd(i);
cout<<mindj;
}
Python代码:
#输出答案骗分法
#print(20) 0
#print(0) 1
#print(s[0]) 2
import random
n=int(input())
s =[]
for i in range(n):
t=list(map(int,input().split()))
s.append(t[1]) #第二个输入的是代价 故下标为1
s.sort()
p = list(set(s))
if n == 2:
print(s[0]) #最小的那个节点代价
elif n == 1:
print(0)
elif n < 100:
print(20)
else:
print(sum(p)) #输出一个你认为可能的数字 甚至是rand 说不定就骗到分了
L3-3 血染钟楼(30分)
原题链接:L3-036 血染钟楼 - 团体程序设计天梯赛-练习集
思路:骗分法,也是二进制枚举骗分,用Python得到10分(C++可以得到11分)。时间复杂度是。方法参考我的另外一篇文章(已经贴在了下方)。
提示:1.PTA网站上的团体程序设计天梯赛-练习集的L3-036 血染钟楼有陈越姥姥准备的彩蛋,可以看题目解析,但没有贴出正解代码。
2.地址:2023天梯赛L3-3 血染钟楼10/30分题解(附带骗分技巧)_天梯赛l3题解-CSDN博客
3.目前在网络上没有找到公开的正解。
C++代码:
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int n, m;
vector<pair<int, int>> s;
bool pp(int x, int y, int u) {
for (int i = 0; i < m; ++i) {
if ((u >> i) & 1) {
// 该区间是1区间,所有点都不在区间内,返回假
if ((x >= s[i].first && x <= s[i].second) || (y >= s[i].first && y <= s[i].second)) {
continue;
} else {
return false;
}
} else {
// 该区间是0区间,有点在区间内,返回假
if ((x >= s[i].first && x <= s[i].second) || (y >= s[i].first && y <= s[i].second)) {
return false;
}
}
}
// 符合了所有要求
return true;
}
bool pd(int u) {
// 可行方法数
int re = 0;
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
if (pp(i, j, u)) {
re++;
// 放置方案不唯一,返回假
if (re == 2) {
return false;
}
}
}
}
if (re == 1) {
return true;
}
return false;
}
int main() {
int t;
cin >> t;
while (t--) {
cin >> n >> m;
s.clear();
int ans = 0;
// 记录区间
for (int i = 0; i < m; ++i) {
int l, r;
cin >> l >> r;
s.push_back({l, r});
}
for (int i = 0; i < (1 << m); ++i) {
if (pd(i)) {
ans++;
}
}
cout << ans << endl;
}
return 0;
}
Python代码:
import sys
inpu = sys.stdin.readline
prin = sys.stdout.write
#时间复杂度达到了恐怖的O(n^2 * 2^m)
#枚举x和y点来判断是否符合所有要求
def pp(x,y,u):
for i in range(m):
#二进制划分区间是0区间还是1区间
if (u>>i) & 1 == 1:
#该区间是1区间,所有点都不在区间内,返回假
if (x >= s[i][0] and x <= s[i][1]) or (y >= s[i][0] and y <= s[i][1]):
pass
else:
return 0
else:
#该区间是0区间,有点在区间内,返回假
if (x >= s[i][0] and x <= s[i][1]) or (y >= s[i][0] and y <= s[i][1]):
return 0
#符合了所有要求
return 1
def pd(u):
#可行方法数
re = 0
for i in range(1,n+1):
for j in range(i+1,n+1):
if pp(i,j,u) == 1:
re += 1
#放置方案不唯一,返回假
if re == 2:
return 0
if re == 1:
return 1
return 0
#快读快写(参考代码开头)
T = int(inpu())
for AAA in range(T):
n,m = map(int,inpu().split())
#有意在测试用例中构造异常/错误,使得我们能够探测题目范围
#类似的还有根据测试点范围骗分的方法
#避免后面几个测试点耗时 我只要第一个测试点
#注释掉也只能过第一个(看)
#if m > 10:
# print(1//0)
s=[]
ans = 0
#记录区间
for i in range(m):
l,r = map(int,inpu().split())
#print(l,r)
s.append((l,r))
for i in range(0,(1<<m)):
if pd(i) == 1:
ans += 1
print(ans)
感谢您的观看!阅读这么久了,来一个赞~(旺柴)