天梯赛题解(Python和C++解法)

L1-043 阅览室

https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7?problemSetProblemId=994805087447138304

C++代码

cpp 复制代码
// 考虑最后一次借书和第一次还书,以及round用法
#include <bits/stdc++.h>
using namespace std;
int n;
map<int,char>mp;
map<int,int>mp1;
signed main()
{
	scanf("%d",&n);
	int cnt=0;
	int sum=0;
	int ci=0;
	while(1)
	{

		int id;
		char ch;
		int h,m;
		scanf("%d %c",&id,&ch);
		scanf("%d:%d",&h,&m);
		if(id==0)
		{
			if(ci==0){
				printf("0 0\n");	
			}
			else
			{
			int u=round(sum*1.0/ci);
			printf("%d %d\n",ci,u);
		    }
			sum=0;
			ci=0;	
			cnt++;
			mp.clear();
			mp1.clear(); 
			if(cnt==n){
				break;
			}
		}
		else
		{
			char pre=mp[id];
			if(pre!='S'&&pre!='E')
			{
				if(ch=='S'){
					mp[id]=ch;
					mp1[id]=h*60+m;
				}	
			}
			else if(pre=='S')
			{
				if(ch=='E'){
					mp[id]='E';
					sum=sum+h*60+m-mp1[id];
					ci++;
				}
				else
				{
					mp[id]='S';
					mp1[id]=h*60+m;
				}
			}	
			else if(pre=='E'){
				if(ch=='S'){
				    mp[id]=ch;
					mp1[id]=h*60+m;
				}
			}	
		}
	}
	return 0;
}

Python代码

python 复制代码
n=int(input())
cnt=0
sum=0
ci=0
mp={}
mp1={}
while(1):
    id,ch,time=map(str,input().split())
    id=int(id)
    h,m=time.split(":")
    h=int(h)
    m=int(m)
    total_min = h * 60 + m  # 提前计算总分钟数,简化代码
    if(id==0):
        if(ci==0):
            print("0 0")
        else:
            u=int(round(sum*1.0/ci))
            print(f"{ci} {u}")
        sum=0

        ci=0
        cnt+=1
        mp.clear()
        mp1.clear()
        if(cnt==n):
            break
    else:
    # 处理mp中无id的情况(对应C++的默认空字符)
        pre = mp.get(id, '')  # 无key时返回空字符串,而非报错
        if pre not in ('S', 'E'):  # 首次访问或非S/E状态
            if ch == 'S':
                mp[id] = ch
                mp1[id] = total_min
        elif pre == 'S':  # 上一次是借书状态
            if ch == 'E':  # 本次还书,统计时长
                mp[id] = 'E'
                sum += total_min - mp1[id]
                ci += 1
            else:  # 再次借书,覆盖时间
                mp[id] = 'S'
                mp1[id] = total_min
        elif pre == 'E':  # 上一次是还书状态
            if ch == 'S':  # 重新借书
                mp[id] = ch
                mp1[id] = total_min

                mp[id]=ch
                mp1[id]=h*60+m

思路

  1. 初始化准备 :首先读取需要统计的批次总数n,同时初始化统计变量(借还总时长sum、有效借还次数ci、已完成批次计数器cnt),以及两个字典缓存(mp存储每本图书的当前借还状态,mp1存储每本图书最后一次借书的时间)。

  2. 循环处理借还记录 :持续读取每一条借还记录,直至完成n个批次的统计:

    • 解析每条记录:将记录中的图书编号id转为整数,借还状态ch保留字符串,时间拆分为小时和分钟后统一转换为总分钟数,简化后续时间差计算。
    • 终止符处理(id=0):触发当前批次的结算逻辑 ------ 若该批次无有效借还记录(ci=0),输出 "0 0";若有有效记录,计算并输出有效借还次数ci和四舍五入后的平均借阅时长(总时长sum除以次数ci后四舍五入并转为整数)。结算后重置总时长、有效次数,清空两个字典缓存,已完成批次计数器加 1,若计数器达到n则终止循环。
    • 有效图书记录处理(id≠0):先安全获取该图书的历史借还状态(无历史状态则返回空字符串),再按状态规则更新:
      • 首次处理该图书(无历史状态):仅当本次状态为借书(S)时,记录该图书的状态为S,并缓存本次借书的总分钟数。
      • 历史状态为借书(S):若本次状态为还书(E),则标记该图书状态为E,计算本次借阅时长(当前时间减缓存的借书时间)并累加到总时长,有效借还次数加 1;若本次仍为借书(S),则覆盖缓存的借书时间,更新状态为S
      • 历史状态为还书(E):仅当本次状态为借书(S)时,重新记录该图书的状态为S,并缓存本次借书的总分钟数。
  3. 批次隔离:每完成一个批次的结算,都会清空存储状态和时间的字典缓存,确保不同批次的借还记录数据隔离,互不干扰。

  4. 数值计算规则 :平均时长计算时,通过sum*1.0/ci确保浮点数除法,再用round四舍五入后转为整数,与 C++ 原版的数值计算逻辑保持一致。

L1-044 稳赢

https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7?problemSetProblemId=994805086365007872

C++代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
signed main()
{
	cin>>n;
	int cnt=0;
	while(1)
	{
		string s;
		cin>>s;
		if(s=="End"){
			break;
		}
		else
		{
			if(cnt==n){
				cnt=0;
				cout<<s<<"\n";
			}
			else
			{
				cnt++;
				if(s=="ChuiZi"){
					cout<<"Bu\n";
				}
				else if(s=="JianDao"){
					cout<<"ChuiZi\n";
				}
				else if(s=="Bu"){
					cout<<"JianDao\n";
				}
			}
		}
	}
 
    return 0;
}

Python代码

python 复制代码
n=int(input())
cnt=0
while(1):
    s=input()
    if(s=="End"):
        break
    else:
        if(cnt==n):
            cnt=0
            print(s)
        else:
            cnt+=1
            if(s=="ChuiZi"):
                print("Bu")
            elif(s=="JianDao"):
                print("ChuiZi")
            elif(s=="Bu"):
                print("JianDao")

思路

  1. 初始化准备 :首先读取一个整数n(表示每轮循环的阈值),并初始化计数器cnt(用于记录当前已处理的非阈值轮次数量)。

  2. 循环处理输入:持续读取输入字符串,直至输入 "End" 时终止循环:

    • 终止条件判断:若输入字符串为 "End",直接跳出循环,结束程序。
    • 非终止输入处理:
      • 若计数器cnt等于阈值n:说明已完成一轮n次的规则转换,此时重置计数器cnt为 0,并直接输出当前输入的字符串(不执行手势转换)。
      • 若计数器cnt小于阈值n:计数器cnt加 1,然后按 "石头剪刀布" 的转换规则输出对应结果:
        • 输入 "ChuiZi(锤子)"→ 输出 "Bu(布)";
        • 输入 "JianDao(剪刀)"→ 输出 "ChuiZi(锤子)";
        • 输入 "Bu(布)"→ 输出 "JianDao(剪刀)"。
  3. 核心逻辑特点

    • n为周期循环:每累计处理n个非终止输入后,第n+1个输入会触发 "重置计数器 + 直接输出" 的逻辑,之后重新开始按规则转换。
    • 规则转换仅针对 "ChuiZi、JianDao、Bu" 三种固定输入,未提及的其他输入(若有)会无输出(代码未处理该情况)。
    • 终止信号 "End" 具有最高优先级,无论当前计数器状态如何,输入后立即终止程序。

L1-046 整除光棍

https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7?problemSetProblemId=994805084284633088

C++代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int r=0;
vector<int>div(vector<int>a,int b)
{
	r=0;
	vector<int>c;
	for(int i=0;i<a.size();i++)
	{
		r=r*10+a[i];
		c.push_back(r/b);
		r%=b;
	}
	reverse(c.begin(),c.end());
	while(c.size()>1&&c.back()==0){
		c.pop_back();
	}
	return c;
}

signed main()
{
	int b;
	cin>>b;
	string s="1";
	while(1)
	{
		vector<int>a;
		for(int i=0;i<s.size();i++){
			a.push_back(s[i]-'0');
		}
		vector<int>c=div(a,b);	
		if(r==0){
			for(int i=c.size()-1;i>=0;i--){
				cout<<c[i];
			}
			cout<<" ";
			cout<<s.size();
			break;
		}else{
			s+="1";
		}
	}

	return 0;
}

Python代码

python 复制代码
r=0
b=input()
s="1"
def div(a,b):
    global r
    r=0
    c=[]
    b=int(b)
    for i in range(0,len(a)):
        r=r*10+a[i]
        c.append(r//b)
        r=r%b
    c.reverse()
    while(len(c)>1 and c[-1]==0):
        c.pop()
    return c
while(1):
    a=[]
    for i in range(0,len(s)):
        a.append(int(s[i]))
    c=div(a,b)
    if(r==0):
        for i in range(len(c)-1,-1,-1):
            print(c[i],end="")
        print(end=" ")
        print(len(s),end="")
        break
    else:
        s+="1"

思路

代码核心思路

该代码的核心目标是:找到由连续数字 "1" 组成的最小正整数(如 1、11、111......),使其能被输入的整数b整除,最终输出该数除以b的商,以及这个由 "1" 组成的数的位数。整体逻辑分为 "手工除法模拟" 和 "候选数枚举" 两大核心模块,具体如下:

1. 初始化准备
  • 定义全局余数变量r(初始为 0),用于存储除法运算后的余数,判断候选数是否能被b整除;
  • 读取输入的除数b(字符串类型,后续在除法函数内转为整数);
  • 初始化候选数字符串s为 "1"(表示第一个待验证的数是 1)。
2. 核心除法函数div(a, b)(模拟手工大整数除法)

该函数实现 "大整数(由列表a存储各位数字)除以整数b" 的手工计算过程,核心目的是计算商和余数:

  • 声明使用全局变量r,重置r为 0,初始化存储商的列表c
  • 将除数b转为整数类型,避免字符串运算错误;
  • 逐位处理大整数(列表a的每一位):① 累计余数:r = r*10 + 当前位数字(模拟手工除法 "带下一位" 的操作,将当前余数与下一位数字合并);② 计算当前位商:将r//b(整除结果)加入商列表c;③ 更新余数:r = r%b(保留当前位除法后的余数,用于后续位计算);
  • 商列表修正:反转商列表(修正手工除法的位序),并去除列表末尾的前导零(仅保留有效商位数,如商列表 [0,0,7] 转为 [7]);
  • 返回修正后的商列表,同时通过全局变量r传递最终余数。
3. 候选数枚举循环(寻找符合条件的 "全 1 数")

通过无限循环构造由 "1" 组成的候选数(1→11→111→......),直至找到能被b整除的数:

  • 候选数转换:将当前字符串s(如 "111")转为整数列表a(如 [1,1,1]),适配除法函数的输入格式;
  • 调用除法函数:传入列表a和除数b,获取商列表c,并通过全局变量r得到余数;
  • 终止条件判断:若余数r=0(当前候选数能被b整除):① 逆序输出商列表的每一位(还原为正常数字顺序);② 输出空格和当前候选数s的长度(即由多少个 "1" 组成);③ 终止循环;
  • 候选数扩展:若余数r≠0(当前候选数无法整除),则在s末尾追加 "1"(构造下一个候选数,如 "11"→"111"),继续循环验证。
4. 核心逻辑特点
  • 采用 "手工除法" 而非直接大整数运算,还原了底层计算过程,避免因超大数运算导致的性能或格式问题;
  • 以 "枚举 + 验证" 的方式寻找最小 "全 1 数",逻辑简单且贴合手工计算思维;
  • 通过全局变量传递余数,确保除法函数的计算结果能被主循环感知,控制循环终止。

L1-049 天梯赛座位分配

https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7?problemSetProblemId=994805081289900032

C++代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
#define x first
#define y second
using namespace std;
const int N=10000;
vector<int>ans[N];
int n;
int cnt[N];
map<int,bool>mp;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>cnt[i];
		cnt[i]*=10;
	}
	if(n==1)
	{
		int st=1;
		while(ans[1].size()<cnt[1]){
			ans[1].push_back(st);
			st+=2;
		}
	}
	else
	{
	int id=1;
	int st=1;
	int sum=0;
	int u=0;
	while(1)
	{
		sum++;
		if(ans[id].size()>=cnt[id]&&mp[id]==false){
			mp[id]=true;
			u++;
		}
		if(sum>10000000) break;
		if(ans[id].size()<cnt[id])
		{
			if(u==n-1){
				st++;
			}
			
			ans[id].push_back(st);
			id++;
			st++;	
		
		}
		else{
			id+=1;
		}
		if(id>n) id=1;
	}
}
	for(int i=1;i<=n;i++)
	{
		cout<<"#"<<i<<"\n";
		vector<int>a=ans[i];
		for(int j=0;j<a.size();j++){
			cout<<a[j];
			if(j%(10)==9) cout<<"\n";
			else cout<<" ";
		}
	}
}

Python代码

python 复制代码
n=int(input())
cntinput=input().split()
mp={}
ans=[[] for i in range(10000)]
cnt=[0]*100000

for i in range(0,n):
    cnt[i+1]=int(cntinput[i])
    cnt[i+1]*=10
if(n==1):
    st=1
    while(len(ans[1])<cnt[1]):
        ans[1].append(st)
        st+=2
else:
    id=1
    st=1
    sum=0
    u=0
    while(1):
        sum+=1
        if(len(ans[id])>=cnt[id] and mp.get(id,0)==False):
            mp[id]=True
            u+=1
        if(sum>100000): break
        if(len(ans[id])<cnt[id]):
            if(u==n-1):
                st+=1
            ans[id].append(st)
            id+=1
            st+=1
        else:
            id+=1
        if(id>n): id=1

for i in range(1,n+1):
    print(f"#{i}")
    a=ans[i]
    for j in range(0,len(a)):
        print(a[j],end="")
        if(j%10==9): print()
        else: print(end=" ")

思路

代码核心思路

该代码的核心目标是:按指定规则为 n 个分组填充连续递增的数值,每个分组需填充至指定长度(输入值 ×10),最终按格式输出每个分组的填充结果。整体逻辑分为 "输入处理、特殊场景填充、普通场景循环填充、结果输出" 四部分,具体如下:

1. 初始化与输入处理
  • 读取分组数量n,以及每个分组的原始填充长度(存入cntinput);
  • 初始化核心数据结构:
    • ans:长度为 10000 的列表的列表,ans[i]存储第i个分组的填充数值;
    • cnt:长度为 100000 的数组,cnt[i]存储第i个分组的目标填充长度(原始长度 ×10),索引从 1 开始匹配分组编号;
    • mp:空字典,用于标记已填满的分组(键为分组 id,值为布尔值)。
  • 遍历输入的原始长度,转换为整数后 ×10,存入cnt[1]~cnt[n](对齐分组编号)。
2. 特殊场景处理(n=1)

仅当分组数为 1 时,单独填充第一个分组:

  • 初始化起始值st=1
  • 循环向ans[1]中添加数值,每次添加后st+2(仅填充奇数),直到ans[1]的长度达到cnt[1](目标长度)。
3. 普通场景处理(n>1):循环填充多个分组

通过无限循环按规则填充各分组,直到循环次数超过 100000(防死循环):

  • 初始化变量:id(当前填充的分组编号,初始为 1)、st(当前要填充的数值,初始为 1)、sum(循环计数器,初始为 0)、u(已填满的分组数量,初始为 0);
  • 每次循环先将sum+1,执行以下逻辑:
    1. 分组填满标记:若当前分组id的填充长度≥目标长度,且未被标记,则标记该分组为已填满,u+1;
    2. 防死循环退出:若sum超过 100000,强制终止循环;
    3. 填充当前分组:若当前分组未填满:
      • 若只剩最后一个分组未填满(u=n-1),st仅 + 1(无额外递增);
      • 向当前分组添加stid+1(切换下一个分组),st+1;
    4. 跳过已填满分组:若当前分组已填满,仅将id+1;
    5. 分组循环:若id超过总分组数n,重置id为 1(循环填充)。
4. 结果输出

按格式输出每个分组的填充结果:

  • 遍历每个分组(1~n),先输出#i(i 为分组编号);
  • 遍历该分组的填充数值,逐个输出:
    • 每输出 10 个数值后换行;
    • 未到 10 个且非最后一个数值时,输出空格分隔;
    • 最后一个数值后不输出多余空格,若最后一行不足 10 个数值,补换行。
核心逻辑特点
  • 分组填充采用 "循环轮询" 方式,确保每个分组按规则填充至目标长度;
  • 对 n=1 的场景单独处理,填充奇数;n>1 时动态调整数值递增规则(只剩最后一个分组时仅 + 1);
  • 用字典标记已填满分组,避免重复统计;设置循环次数上限,防止死循环;
  • 输出格式严格对齐 "每 10 个换行、数值间空格分隔" 的规则,保证输出整洁。

L1-050 倒数第N个字符串

https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7?problemSetProblemId=994805080346181632

C++代码

cpp 复制代码
// 看成是正数的第几个,考虑26进制即可,注意不足n位前面补a;
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,tar;
map<int,char>mp;
signed main()
{
	char ch='a';
	for(int i=1;i<=26;i++){
		mp[i]=ch++;
	}
	cin>>n>>tar;
	int x=pow(26,n)-tar;
	vector<int>ans;
	if(x==0){
		for(int i=0;i<n;i++){
			cout<<"a";
		}
	}
	else
	{
	while(x)
	{
		int u=x%26;
		u++;
		ans.push_back(u);
		x/=26;
	}
	if(ans.size()<n){
		int u=n-ans.size();
		for(int i=0;i<u;i++) cout<<"a";
	}
	for(int i=ans.size()-1;i>=0;i--){
		cout<<mp[ans[i]];
	}
	
}
    return 0;
}

Python代码

python 复制代码
import math

mp={}
ch='a'
id=ord(ch)
for i in range(1,27):
    mp[i]=chr(id)
    id+=1

n,tar=map(int,input().split())
x=math.pow(26,n)-tar
ans=[]
if(x==0):
    for i in range(0,n):
        print("a",end="")
else:
    while(x):
        u=x%26
        u+=1
        ans.append(u)
        x=x//26
    if(len(ans)<n):
        u=n-len(ans)
        for i in range(0,u):
            print("a",end="")
    for i in range(len(ans)-1,-1,-1):
        print(mp[ans[i]],end="")

思路

代码核心思路

该代码的核心目标是:根据输入的位数n和目标值tar,计算并输出一个长度为n的小写字母字符串,核心逻辑是将26^n - tar的结果按特殊规则转换为 26 进制字母表示(不足n位时前面补 'a')。整体流程分为 "映射初始化、核心计算、特殊值处理、进制转换、补位输出" 五部分,具体如下:

1. 数字 - 字母映射初始化

构建 1 到 26 与小写字母 a 到 z 的一一对应关系:

  • 初始化空字典mp,以数字 1~26 为键,对应的小写字母 a~z 为值;
  • 通过 ASCII 码操作生成字母:先获取 'a' 的 ASCII 码,循环 1~26 时,每次将 ASCII 码 + 1 并转回字符,存入字典(如mp[1]='a'mp[2]='b'...mp[26]='z')。
2. 输入处理与核心数值计算
  • 读取两个整数输入:n(最终输出字符串的固定长度)、tar(目标偏移值);
  • 计算核心值x = 26^n - tar(即 26 的 n 次方减去 tar),将问题转化为对x的 26 进制转换。
3. 特殊值处理(x=0)

x等于 0,直接输出n个连续的 'a'(因为 26^n - tar=0 意味着 tar=26^n,对应最小的字母组合 "aaa...a")。

4. 26 进制分解(偏移调整)

x≠0,对x进行 26 进制分解,且每一步做偏移调整:

  • 初始化空列表ans存储分解后的数字;
  • 循环分解x
    1. x除以 26 的余数u,将u+1(偏移调整,使余数范围从 0~25 转为 1~26,匹配字典的键范围);
    2. 将调整后的u存入ans列表;
    3. x更新为x除以 26 的整数商(继续分解高位);
    4. 直到x变为 0 时停止分解。
5. 补位与结果输出

将分解后的数字转换为字母并输出,确保总长度为n

  • 补位:若ans列表的长度(分解后的位数)小于n,计算需要补充的 'a' 的数量(n - len(ans)),并连续输出对应数量的 'a';
  • 逆序输出:逆序遍历ans列表(因为进制分解是从低位到高位存储),根据字典mp将每个数字转换为对应字母并输出,最终拼接成长度为n的字符串。
核心逻辑特点
  • 本质是 "反向 26 进制转换":常规 26 进制是 0~25 对应 a~z,此处通过余数+1调整为 1~26 对应 a~z,适配 "26^n - tar" 的计算逻辑;
  • 补位规则保证输出字符串长度严格等于n,不足时前置补 'a',符合 "不足 n 位前面补 a" 的要求;
  • 利用 ASCII 码生成字母映射,避免手动枚举,简洁且不易出错。
相关推荐
Tony Bai3 小时前
高并发后端:坚守 Go,还是拥抱 Rust?
开发语言·后端·golang·rust
wjs20243 小时前
Swift 类型转换
开发语言
秃了也弱了。4 小时前
python实现定时任务:schedule库、APScheduler库
开发语言·python
Dfreedom.4 小时前
从 model(x) 到__call__:解密深度学习框架的设计基石
人工智能·pytorch·python·深度学习·call
weixin_440730504 小时前
java数组整理笔记
java·开发语言·笔记
weixin_425023004 小时前
Spring Boot 配置文件优先级详解
spring boot·后端·python
Thera7774 小时前
状态机(State Machine)详解:原理、优缺点与 C++ 实战示例
开发语言·c++
linux开发之路4 小时前
C++高性能日志库开发实践
c++·c++项目·后端开发·c++新特性·c++校招
niucloud-admin5 小时前
java服务端——controller控制器
java·开发语言
刻BITTER5 小时前
在TRAE 上安装PlatformIO
c++·单片机·嵌入式硬件·arduino