SMU 2026 Spring 天梯赛7题解
L1-4 记小本本
题意:
模拟两个按钮:输入1(红色按钮)表示吵架次数加1,输入0(绿色按钮)输出当前吵架次数。输入遇到非0非1时结束。
思路:
维护计数器,遇到1时增加,遇到0时输出当前值,遇到其他数字时结束程序。
代码
点击查看代码
void solve() {
int x, cnt = 0;
while (cin >> x) {
if (x == 1) cnt++;
else if (x == 0) cout << cnt << endl;
else break;
}
}
L1-5 大勾股定理
题意:
给定正整数 n,需要找出 2n+1 个连续正整数,使得前 n+1 个数的平方和等于后 n 个数的平方和。
思路:
通过打表或数学推导,可以计算出起始数 a[0] = 2n² + n。之后直接输出从 a[0] 开始的连续 2n+1 个数即可。
代码
点击查看代码
void solve() {
int n;
cin >> n;
int s = 2 * n * n + n;
for (int i = 0; i <= n; ++i) {
cout << s + i << "^2";
if (i < n) cout << " + ";
else cout << " =" << endl;
}
for (int i = n + 1; i <= 2 * n; ++i) {
cout << s + i << "^2";
if (i < 2 * n) cout << " + ";
else cout << endl;
}
}
L1-6 Wifi密码
题意:
给定 N 行,每行包含 4 个形如“X-Y”的字符串,其中 X 为 A、B、C、D 之一,Y 为 T(正确)或 F(错误)。每道题只有一个正确选项。字母对应数字:A-1、B-2、C-3、D-4。要求将每道题的正确选项对应的数字按顺序拼接,输出 wifi 密码。
思路:
直接模拟即可。对于每行输入的 4 个字符串,检查第三个字符(下标 2)是否为 'T',若是则根据第一个字符输出对应数字。按行顺序处理并输出。
代码
点击查看代码
void solve() {
int n;
cin >> n;
string s;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < 4; ++j) {
cin >> s;
if (s[2] == 'T') {
char c = s[0];
if (c == 'A') cout << 1;
else if (c == 'B') cout << 2;
else if (c == 'C') cout << 3;
else if (c == 'D') cout << 4;
}
}
}
cout << endl;
}
L1-7 乘法口诀数列
题意:
给定两个一位数字 \(a_1\) 和 \(a_2\),以及整数 \(n\)。按照规则生成数列 \(\{a_n\}\):从 \(a_1\) 开始,每次将当前数字与下一个数字相乘,将结果贴在数列末尾。如果结果不是一位数,则其每一位都成为数列的一项。输出数列的前 \(n\) 项。
思路:
直接模拟生成过程。用数组存储数列,初始包含 \(a_1\) 和 \(a_2\)。用指针 \(i\) 从 0 开始,每次计算 \(v[i] \times v[i+1]\),根据乘积位数(一位或两位)将数字添加到数组末尾,直到数组长度达到 \(n\)。最后输出前 \(n\) 项。
代码
点击查看代码
void solve() {
int a1, a2, n;
cin >> a1 >> a2 >> n;
vector<int> v;
v.push_back(a1);
v.push_back(a2);
int i = 0;
while (v.size() < n) {
int p = v[i] * v[i + 1];
if (p >= 10) {
v.push_back(p / 10);
v.push_back(p % 10);
} else {
v.push_back(p);
}
i++;
}
for (int j = 0; j < n; j++) {
if (j) cout << " ";
cout << v[j];
}
cout << endl;
}
L1-8 新年烟花
题意:
活动场地为 \(N \times M\) 的网格,每个格子可能是空格子(0)、烟花格子(负整数)或被占据的格子(正整数,表示高度)。给定小 C 的身高 \(H\),一个空格子是优秀观赏位置当且仅当它能看到至少三个不同烟花。一个空格子能看到一个烟花当且仅当两者在同一行或同一列,且连线上的所有格子要么是空格子,要么其高度小于 \(H\)。求优秀观赏位置的个数,以及能看到最多烟花的格子坐标(若有多个,取行号最小,行号相同取列号最小的)。
思路:
由于 \(N, M \leq 50\),直接暴力枚举每个空格子,分别向四个方向(上、下、左、右)扫描,统计能看到的烟花数量。扫描时,若遇到烟花则计数,若遇到高度大于等于 \(H\) 的物体则停止(该方向后续不可见)。若计数 \(\geq 3\) 则为优秀观赏位置。同时记录最大计数及对应坐标即可。
代码
点击查看代码
void solve() {
int n, m, h;
cin >> n >> m >> h;
vector<vector<int>> a(n, vector<int>(m));
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
cin >> a[i][j];
int cnt = 0, mi = -1, mj = -1, mx = -1;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (a[i][j] != 0) continue;
int ss = 0;
for (int x = i - 1; x >= 0; --x) {
if (a[x][j] < 0) ++ss;
if (a[x][j] > 0 && a[x][j] >= h) break;
}
for (int x = i + 1; x < n; ++x) {
if (a[x][j] < 0) ++ss;
if (a[x][j] > 0 && a[x][j] >= h) break;
}
for (int y = j - 1; y >= 0; --y) {
if (a[i][y] < 0) ++ss;
if (a[i][y] > 0 && a[i][y] >= h) break;
}
for (int y = j + 1; y < m; ++y) {
if (a[i][y] < 0) ++ss;
if (a[i][y] > 0 && a[i][y] >= h) break;
}
if (ss >= 3) {
++cnt;
if (ss > mx || (ss == mx && (i < mi || (i == mi && j < mj)))) {
mx = ss;
mi = i;
mj = j;
}
}
}
}
cout << cnt << endl;
cout << mi << " " << mj << endl;
}
L2-1 插松枝
题意:
模拟插松枝的过程。有推送器(队列)、小盒子(栈)和松枝(当前制作中的序列)。推送器顺序推出松针片,小盒子容量为 M,松枝最多插 K 片。每次取松针时,若小盒子非空则检查栈顶是否满足不大于当前松枝最后一片(若松枝为空则任意满足),满足则取用;否则从推送器取一片,若满足则用,否则若小盒子未满则放入小盒子,继续从推送器取;若小盒子已满则结束当前松枝并将刚取的松针压回推送器。当小盒子栈顶不满足且推送器空,或松枝已插满 K 片时也结束当前松枝。输出所有成品松枝上松针的大小(自底向上即插入顺序)。
思路:
按照题意模拟即可。用双端队列或栈表示推送器(支持前端压回),栈表示小盒子,向量记录当前松枝。外层循环处理每根松枝,内层循环不断尝试取松针插入,直到满足结束条件。注意各种边界情况,如小盒子满且推送器取到不满足需压回。
代码
点击查看代码
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector<int>q, st;
vector<vector<int>>ans;
vector<int>qq;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
qq.push_back(x);
}
reverse(qq.begin(), qq.end());
while (qq.size()) {
int x;
if (st.size() && (q.size() == 0 || (q.size() < k && q.back() >= st.back()))) {
x = st.back();
st.pop_back();
q.push_back(x);
if (q.size() == k) {
ans.push_back(q);
q.clear();
}
} else {
x = qq.back();
qq.pop_back();
if (q.size() == 0 || (q.size() < k && q.back() >= x)) {
q.push_back(x);
if (q.size() == k) {
ans.push_back(q);
q.clear();
}
} else {
if (st.size() < m) {
st.push_back(x);
} else {
ans.push_back(q);
q.clear();
qq.push_back(x);
}
}
}
// for (auto x : st) {
// cout << x << ' ';
// }
// cout << endl;
}
while (st.size()) {
int x = st.back();
st.pop_back();
if (q.size() == 0 || (q.size() < k && q.back() >= x)) {
q.push_back(x);
} else {
ans.push_back(q);
q.clear();
q.push_back(x);
}
}
if (q.size())ans.push_back(q);
for (auto v : ans) {
int f = 1;
for (auto x : v) {
if (f) {
f = 0;
} else {
cout << ' ';
}
cout << x;
}
cout << endl;
}
}
L2-2 实验室使用排期
题意:
给定 N 个时间段(开始时间和结束时间),同一时刻实验室内最多只能有一个人。求最多能批准多少个互不重叠的时间段。
思路:
经典的贪心。将所有时间段按结束时间从小到大排序,然后贪心选择:每次选择结束时间最早且开始时间不早于上一个选中结束时间的申请。
代码
点击查看代码
int to(sring s){
int h=(s[0]-'0')*10+(s[1]-'0');
int m=(s[3]-'0')*10+(s[4]-'0');
int ss=(s[6]-'0')*10+(s[7]-'0');
return h*3600+m*60+ss;
}
void solve(){
int n;
cin>>n;
vector<PII> a;
for(int i=0;i<n;i++){
sring s1,s2;
cin>>s1>>s2;
int s=to(s1),t=to(s2);
a.push_back({t,s});
}
sort(a.begin(),a.end());
int ls=-1,cnt=0;
for(auto [t,s]:a){
if(s>=ls){
cnt++;
ls=t;
}
}
cout<<cnt<<endl;
}
L2-3 到底爱不爱我
题意:
给定N个情枝,每个情枝有类型(1:专情,2:博爱,3:情变)和左右分枝连接的编号(类型3只有左分枝)。末梢编号为0。从初始一枝(入度为0的节点)出发,按先左后右的顺序遍历所有分枝,遇到分枝连接0则记录为一个末梢。有K次询问,每次给出一个01串,按遍历顺序依次给每个末梢赋值(0:不爱,1:爱)。根据情枝的类型和分枝的输入值(来自子节点或末梢)计算初始一枝的根部输出值,输出“Ai”或“BuAi”。
思路:
首先找到根节点(入度为0)。然后DFS遍历,记录每个节点左右分枝对应的末梢索引(若分枝为0)。对于每次询问,递归计算每个节点的值,节点类型为1相当于与门;类型为2相当于或门;类型为3相当于非门。递归时若分枝连接子节点则递归计算,若为末梢则取询问串对应位置的值。
代码
点击查看代码
vector<int>g[N];
int a[N];
int b[N];
int p=0;
int d[N];
string s;
int dfs(int x){
if(x==0){
return s[p++]-'0';
}
if(a[x]==1){
b[x]=dfs(g[x][0])&dfs(g[x][1]);
}else if(a[x]==2){
b[x]=dfs(g[x][0])|dfs(g[x][1]);
}else{
b[x]=dfs(g[x][0])^1;
}
return b[x];
}
void solve() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
int t;
cin>>t;
a[i]=t;
if(t==1){
int x,y;
cin>>x>>y;
g[i].push_back(x),d[x]++;
g[i].push_back(y),d[y]++;
}else if(t==2){
int x,y;
cin>>x>>y;
g[i].push_back(x),d[x]++;
g[i].push_back(y),d[y]++;
}else{
int x;
cin>>x;
g[i].push_back(x),d[x]++;
}
}
int rt;
for(int i=1;i<=n;i++){
if(!d[i]){
rt=i;
break;
}
}
int k;
cin>>k;
for(int i=0;i<k;i++){
p=0;
cin>>s;
dfs(rt);
if(b[rt])cout<<"Ai\n";
else cout<<"BuAi\n";
}
}
L2-4 势均力敌
题意:
给定 n 个不同的个位数字(1‑9),用它们组成 n! 个不同的 n 位数。需要将这些数分成两组,每组个数相等(均为 n!/2),且两组的平方和相等。输出其中一组数。
思路:
n ≤ 4,n! 最大为 24,可以直接暴力生成所有排列,然后搜索分组。生成所有排列后,计算每个数的平方值。使用深度优先搜索将每个数分配到组 A 或组 B,搜索时剪枝:若组 A 或组 B 的个数超过一半,或组 A 的平方和超过总平方和的一半,则剪枝;当一组已满时,后续数只能分配到另一组。搜索到解后输出组 A 的所有数即可。
由于n很小,搜索完直接输出答案即可。
我这里是直接猜的答案,没写搜索过程。
代码
点击查看代码
void solve() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
if(n==3){
cout<<a[1]<<a[2]<<a[3]<<endl;
cout<<a[3]<<a[1]<<a[2]<<endl;
cout<<a[2]<<a[3]<<a[1]<<endl;
}else{
cout<<a[1]<<a[2]<<a[3]<<a[4]<<endl;
cout<<a[2]<<a[3]<<a[4]<<a[1]<<endl;
cout<<a[3]<<a[4]<<a[1]<<a[2]<<endl;
cout<<a[4]<<a[1]<<a[2]<<a[3]<<endl;
cout<<a[2]<<a[1]<<a[3]<<a[4]<<endl;
cout<<a[1]<<a[3]<<a[4]<<a[2]<<endl;
cout<<a[3]<<a[4]<<a[2]<<a[1]<<endl;
cout<<a[4]<<a[2]<<a[1]<<a[3]<<endl;
cout<<a[1]<<a[4]<<a[2]<<a[3]<<endl;
cout<<a[4]<<a[2]<<a[3]<<a[1]<<endl;
cout<<a[2]<<a[3]<<a[1]<<a[4]<<endl;
cout<<a[3]<<a[1]<<a[4]<<a[2]<<endl;
}
}
L3-1 City 不 City
题意:
给定 n 个城镇和 m 条双向道路,每个城镇有一个旅游热度。求从起点 s 到终点 t 的最短路径长度。若存在多条最短路径,则选择路径中途径城镇(不包括起点和终点)的最高热度值最小的那条。输出最短路径长度和该最高热度值(若无途径城镇则输出 0)。若不可达,输出 "Impossible"。
思路:
使用 Dijkstra 算法,双关键字优先队列维护。第一关键字为从起点到当前点的路径长度,第二关键字为途径城镇(不包括起点和当前点,若当前点为终点则不包括)的最高热度值。每次更新时,若新路径长度更短,或长度相同但最高热度值更小,则更新。注意起点终点热度不计入。
代码
点击查看代码
vector<PII>g[N];
int a[N];
int mn[N];
int mn1[N];
void solve() {
int n,m,s,t;
cin>>n>>m>>s>>t;
for(int i=1;i<=n;i++){
cin>>a[i];
mn[i]=mn1[i]=1e18;
}
for(int i=1;i<=m;i++){
int x,y,w;
cin>>x>>y>>w;
g[x].push_back({y,w});
g[y].push_back({x,w});
}
priority_queue<PIII,vector<PIII>,greater<PIII>>q;
q.push({{0,0},s});
mn[s]=0;
mn1[s]=0;
while(q.size()){
auto [u,x]=q.top();
q.pop();
auto [s1,s2]=u;
for(auto [y,w]:g[x]){
int ss2=max(s2,a[y]);
if(y==t){
ss2=s2;
}
if(s1+w<mn[y]||(s1+w==mn[y]&&ss2<mn1[y])){
mn[y]=s1+w;
mn1[y]=ss2;
q.push({{mn[y],mn1[y]},y});
}
}
}
if(mn[t]!=1e18)cout<<mn[t]<<' '<<mn1[t]<<endl;
else cout<<"Impossible";
}
L3-2 逆序对
题意:
给定一个 1 到 n 的排列,对于每一对下标 i ≤ j,计算将子数组 [i, j] 逆转后,整个序列的逆序对个数。需要输出所有 n(n+1)/2 个结果,按 i 优先的顺序。
思路:
设原序列逆序对总数为 tot。逆转子数组 [i, j] 后,逆序对的变化仅发生在子数组内部。设子数组长度为 len = j-i+1,其内部的逆序对数由原来的 inv 变为 C(len,2) - inv。因此新逆序对数为 tot - 2*inv + C(len,2)。问题转化为快速计算所有子数组的逆序对数 inv(i,j)。可用动态规划或者二维前缀和在 \(O(n^2)\) 时间复杂度计算。
\(\displaystyle inv(i,j)=\sum_{x=i}^{j}\sum_{y=x}^{j}[a_i>a_j]\).
代码
点击查看代码
int a[N];
int f[1005][1005];
void solve() {
int n, m, s, t;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (a[i] > a[j]) {
f[i][j]++;
}
}
}
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= n; j++) {
f[i][j] += f[i][j - 1];
}
}
for (int j = 1; j <= n; j++) {
for (int i = n; i >= 1; i--) {
f[i][j] += f[i+1][j];
}
}
int ff=1;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if(ff){
ff=0;
}else{
cout<<' ';
}
cout<<f[1][n]-2*f[i][j]+(j-i+1)*(j-i)/2;
}
}
}
L3-3 攀岩
题意:
机器人有三个长度不超过 \(r\) 的机械臂,初始时两个机械臂分别抓住岩点 \(p_1\) 和 \(p_2\),核心和第三个机械臂位置任意。目标是有两个机械臂同时抓住 \(p_n\)。移动过程中需始终保持至少两个机械臂抓住两个不同的岩点。求能完成攀岩任务的最短机械臂长度 \(r\)。
思路:
代码
点击查看代码

浙公网安备 33010602011771号