SMU 2026 Spring 天梯赛6题解
7-4 小孩子才做选择,大人全都要
题意:
给定两个整数 a 和 b,正数表示狗粮的克数,负数表示储蓄盒的容量(绝对值)。阿汪会选择狗粮最多的盒子(只考虑正数),铲屎官会两个盒子都要。铲屎官打开盒子后,如果遇到储蓄盒,会用另一只盒子里的狗粮来装满它(不够的话自己买)。输出阿汪选择时能吃到狗粮的克数 A 和铲屎官全都要后阿汪能吃到狗粮的克数 B,并判断阿汪是赚了(B > A)、亏了(B < A)还是躺平(A = B = 0)。
思路:
- 阿汪选择时能吃到狗粮的克数 A:取两个数中最大的正数,如果没有正数则为 0。
- 铲屎官全都要后阿汪能吃到狗粮的克数 B:
- 两个都是正数:狗粮总和。
- 一正一负:用正数装满负数,剩余部分为正则给阿汪,否则为 0。
- 两个都是负数:阿汪得到 0。
- 比较:
- 如果 A = B = 0,输出躺平脸。
- 如果 B > A,阿汪赚了,输出笑脸。
- 否则阿汪亏了,输出哭脸。
代码
点击查看代码
void solve() {
int n,m;
cin>>n>>m;
if(n>0&&m>0){
cout<<max(n,m)<<" "<<n+m<<endl;
cout<<"^_^\n";
}else if(n<0&&m<0){
cout<<0<<" "<<0<<endl;
cout<<"-_-\n";
}else{
cout<<max(n,m)<<' '<<max(0ll,n+m)<<endl;
cout<<"T_T\n";
}
}
7-5 吃火锅
题意:
读入多行字符串,直到遇到单独一行只有一个英文句点"."为止。统计总行数(不包括结束行),然后检查每一行是否包含子串"chi1 huo3 guo1"。输出总行数,然后如果至少有一行包含该子串,则输出第一次出现的行号(从1开始)以及包含该子串的总行数;否则输出"-_-#"。
思路:
直接遍历每一行,使用string的find方法查找子串,统计出现次数并记录第一次出现的位置。
代码
点击查看代码
void solve() {
int n=0;
string s;
int mi=0,ss=0;
while(getline(cin,s)){
if(s==".")break;
n++;
if(s.find("chi1 huo3 guo1")!=-1){
if(!mi)mi=n;
ss++;
}
}
cout<<n<<endl;
if(mi)cout<<mi<<' '<<ss<<endl;
else cout<<"-_-#\n";
}
7-6 外星人的一天
题意:
给定 N 个地球人的时刻,格式为 Day hh:mm,其中 Day 取值 0~6 对应周日至周六。需要转换为外星人的时刻。外星人的一天有 48 小时,一周只工作三天(对应地球的周一至周六),周日休息。转换规则:若地球人是周日(Day=0),则直接输出原时间;否则,将地球人时间除以 2(分钟向下取整),并将地球人的星期几映射到外星人的星期几(地球人周一和周二对应外星人周一,周三和周四对应外星人周二,周五和周六对应外星人周三)。
思路:
对于每个输入,若 Day=0 直接输出。否则,利用奇偶性合并地球人的两天为外星人的一天:若 Day 为偶数,则先将小时加上 24,使得该天的时间相当于从前一天开始累计。然后外星人 Day 计算为 (d+1)/2。接着计算总分钟数 m + h*60,除以 2 得到外星人总分钟数,再转换为小时和分钟输出即可。
代码
点击查看代码
void solve() {
int d,h,m;
char x;
string hh,mm;
cin>>d>>h>>x>>m;
if(d==0){
hh=to_string(h);
mm=to_string(m);
if(hh.size()<2)hh='0'+hh;
if(mm.size()<2)mm='0'+mm;
cout<<d<<' '<<hh<<':'<<mm<<endl;
return;
}
if(d%2==0){
h+=24;
}
d=(d+1)/2;
m+=h*60;
m/=2;
h=m/60;
m%=60;
hh=to_string(h);
mm=to_string(m);
if(hh.size()<2)hh='0'+hh;
if(mm.size()<2)mm='0'+mm;
cout<<d<<' '<<hh<<":"<<mm<<endl;
}
7-7 随机输一次
题意:
给定 \(N\) 个正整数 \(K_i\),表示第i次输局之后应该隔 \(K_{i+1}\) 次再让下一个输局。然后循环输入对方的出招("ChuiZi"、"JianDao"、"Bu"),需要根据当前应该是赢还是输,输出对应的克制或输招。赢局输出克制对手的招,输局输出被对手克制的招。
思路:
用0、1、2分别表示锤子、剪刀、布,映射关系为:锤子克剪刀(0>1),剪刀克布(1>2),布克锤子(2>0)。赢局时输出对手招数+2再对3取模,输局时输出对手招数+1再对3取模。用计数器cnt记录当前连续赢的次数,当cnt等于当前K值时输一局并重置cnt,然后取下一个K值(循环使用)。
代码
点击查看代码
int a[N];
map<string,int>mp;
string ss[3]={"ChuiZi","JianDao","Bu"};
void solve() {
mp["ChuiZi"]=0;
mp["JianDao"]=1;
mp["Bu"]=2;
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
int p=0,s=0;
string t;
while(cin>>t){
if(t=="End")break;
s++;
// cout<<s<<' '<<a[p]<<endl;
if(s==a[p]+1){
cout<<ss[(mp[t]+1)%3]<<endl;
s=0;
p++;
p%=n;
}else{
cout<<ss[(mp[t]+2)%3]<<endl;
}
}
}
7-8 谷歌的招聘
题意:
给定一个长度为 L 的数字字符串 N,找出最早出现的长度为 K 的子串,使得这个子串表示的整数是素数。如果不存在,输出 404。
思路:
遍历所有长度为 K 的子串,将每个子串转换为整数,判断是否为素数。素数判断采用试除法:预先用线性筛法求出所有小于等于 \(10^6\) 的素数,对于每个待判断的数,若小于等于 \(10^6\) 直接查表,否则用素数表中的素数试除。
代码
点击查看代码
bool flg[N];
int tot = 0;
void getMu(int n) {
mu[1] = 1;
flg[0]=flg[1]=1;
for (int i = 2; i <= n; ++i) {
if (!flg[i]) p[++tot] = i, mu[i] = -1;
for (int j = 1; j <= tot && i * p[j] <= n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
// for(int i=2;i<=n;i++){
// mu[i]+=mu[i-1];
// }
}
//int fac[N], inv[N];
int qpow(int x, int n) {
int ans = 1;
while (n) {
if (n & 1) {
ans *= x;
}
x *= x;
n >>= 1;
}
return ans;
}
int a[N];
string s;
bool cmp(int x){
// cout<<x<<' '<<flg[x]<<endl;
if(x<=1e6)return !flg[x];
int pp=1;
while(x>1&&pp<=tot&&p[pp]*p[pp]<=x){
if(x%p[pp]==0)return 0;
pp++;
}
return 1;
}
int f(int l,int r){
int x=0;
for(int i=l;i<=r;i++){
x*=10;
x+=s[i]-'0';
}
return x;
}
void solve() {
getMu(1e6);
int L,K;
cin>>L>>K;
cin>>s;
for(int i=0;i+K-1<L;i++){
if(cmp(f(i,i+K-1))){
for(int j=i;j<i+K;j++)cout<<s[j];
return;
}
}
cout<<404<<endl;
}
7-9 三足鼎立
题意:
给定本国实力值P和n个其他国家的实力值。求有多少种选择两个国家结盟的方式,使得三个国家(本国和选中的两个国家)满足“三足鼎立”:任意两国的实力之和大于第三国。
思路:
题意等价于,在n个数中选两个和P组成三角形的三边。
设选中的两个国家实力为a和b,考虑三角形的性质,条件等价于|a-b| < P 且 a+b > P。将数组排序后,对于每个a[i],需要找到j>i使得a[j] > P-a[i]且a[j] < a[i]+P。通过二分查找上下界,累加区间长度即可。
代码
点击查看代码
void solve() {
int n;
int P;
int ans=0;
cin>>n>>P;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
if(a[i]>=P){
int p1=upper_bound(a+1,a+1+n,a[i]-P)-a-1;
int p2=lower_bound(a+1,a+1+n,a[i]+P)-a-1;
p1=max(p1,i);
// cout<<p1<<' '<<p2<<endl;
//
// cout<<a[p1+1]<<' '<<a[p2]<<endl;
ans+=max(0ll,p2-p1);
}else{
int p1=upper_bound(a+1,a+1+n,P-a[i])-a-1;
int p2=lower_bound(a+1,a+1+n,a[i]+P)-a-1;
p1=max(p1,i);
// cout<<p1<<' '<<p2<<endl;
// cout<<a[p1+1]<<' '<<a[p2]<<endl;
ans+=max(0ll,p2-p1);
}
}
cout<<ans<<endl;
}
7-10 哈利·波特的考试
题意:
给定一个无向带权图,有 \(n\) 个节点(动物)和 \(m\) 条边(魔咒),每条边有长度(魔咒字符数)。要求找到一个节点 \(x\),使得 \(x\) 到所有其他节点的最短距离中的最大值最小化。即选择 \(x\) 使得 \(\max_{1 \le i \le n} dist(x,i)\) 最小,其中 \(dist(x,i)\) 是 \(x\) 到 \(i\) 的最短距离。如果存在多个满足条件的 \(x\),输出编号最小的。如果图不连通(存在节点不可达),则输出 0。
思路:
使用 Floyd 算法求出所有节点对之间的最短距离,复杂度 \(O(n^3)\)。然后对每个节点 \(i\),计算它到其他节点的最远距离 \(max_i\),即该节点变为最难变的动物所需的魔咒长度。取所有 \(max_i\) 中最小的对应的节点 \(i\),若有多个取编号最小。若存在节点 \(i\) 到某个节点不可达(距离为无穷大),则该 \(max_i\) 无效,如果所有节点都有不可达的情况,输出 0。
代码
点击查看代码
int mn[105][105];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i != j)mn[i][j] = 1e18;
else mn[i][j] = 0;
}
}
for (int i = 1; i <= m; i++) {
int x, y, w;
cin >> x >> y >> w;
mn[x][y] = min(mn[x][y], w);
mn[y][x] = mn[x][y];
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
mn[i][j] = min(mn[i][j], mn[i][k] + mn[k][j]);
}
}
}
int mi = 0, mx = 1e18;
for (int i = 1; i <= n; i++) {
int ss = 0;
for (int j = 1; j <= n; j++) {
ss = max(ss, mn[i][j]);
}
if (ss < mx) {
mx = ss;
mi = i;
}
}
if (mi)cout << mi << ' ' << mx << endl;
else cout << 0 << endl;
}
7-11 城市间紧急救援
题意:
给定n个城市,每个城市有一定数量的救援队,以及连接城市的m条道路和长度。从起点s到终点d,求:
- 最短路径的数量
- 最短路径中能召集的最多救援队数量(包括起点终点)
- 输出具体的路径
思路:
Dijkstra算法的变形。在求最短路的同时,维护:
- 最短路数量
- 救援队最大值
- 前驱节点用于输出路径
当找到更短的路径时,更新距离、救援队数量、路径数和前驱。
当路径长度相同时,如果救援队数量更大,更新救援队数量和前驱;同时累加路径数量。
代码
点击查看代码
int a[N];
int sum[N],vis[N];
int lst[N];
vector<PII>g[N];
PII mn[N];
void solve(){
int n,m,s,d;
cin>>n>>m>>s>>d;
for(int i=0;i<n;i++){
cin>>a[i];
mn[i]={1e18,0};
}
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>,less<PIII>>q;
q.push({{0,a[s]},s});
mn[s]={0,a[s]};
sum[s]=1;
while(q.size()){
auto [u,x]=q.top();
q.pop();
if(vis[x])continue;
vis[x]=1;
auto [s1,s2]=u;
s1=-s1;
for(auto [y,w]:g[x]){
int ss1=s1+w;
int ss2=s2+a[y];
if(ss1<mn[y].first){
mn[y]={ss1,ss2};
sum[y]=sum[x];
lst[y]=x;
q.push({{-ss1,ss2},y});
}else if(ss1==mn[y].first){
sum[y]+=sum[x];
if(ss2>mn[y].second){
mn[y]={ss1,ss2};
lst[y]=x;
q.push({{-ss1,ss2},y});
}
}
}
}
cout<<sum[d]<<" "<<mn[d].second<<endl;
vector<int>ans;
int x=d;
while(x!=s){
ans.push_back(x);
x=lst[x];
}
ans.push_back(s);
reverse(ans.begin(),ans.end());
int ff=1;
for(auto x:ans){
if(ff){
ff=0;
}else{
cout<<' ';
}
cout<<x;
}
}
7-12 智能护理中心统计
题意:
有N个老人和若干个管理结点构成一棵树。管理结点用不超过4个大写字母表示,老人用1-N的编号表示。每个结点有唯一的上级,系统根结点没有上级。有三种操作:
- T x y:将编号为x的老人转移到管理结点y下
- Q y:查询管理结点y所负责的所有老人数量(包括其子树中的所有老人)
- E:结束程序
思路:
用并查集思想存储父子关系,每个结点的父节点为其直接上级。对于转移操作,需要将老人从其原父节点的孩子列表中删除,然后加入新父节点的孩子列表。对于查询操作,从该结点开始DFS遍历其子树,统计所有老人编号(≤N)的数量。
代码
点击查看代码
vector<int> g[N];
int fa[N];
unordered_map<string, int> mp;
int n, m, p;
void solve() {
cin >> n >> m;
p = n + 1;
for (int i = 0; i < m; i++) {
string a, b;
cin >> a >> b;
int x, y;
if (a[0] >= '0' && a[0] <= '9') x = stoi(a);
else {
if (!mp.count(a)) mp[a] = p++;
x = mp[a];
}
if (!mp.count(b)) mp[b] = p++;
y = mp[b];
fa[x] = y;
g[y].push_back(x);
}
string s;
while (cin >> s) {
if (s == "T") {
int x;
string y;
cin >> x >> y;
int yy = mp[y];
if (fa[x] == yy) continue;
for (int i = 0; i < g[fa[x]].size(); i++) {
if (g[fa[x]][i] == x) {
swap(g[fa[x]][i], g[fa[x]].back());
g[fa[x]].pop_back();
break;
}
}
g[yy].push_back(x);
fa[x] = yy;
} else if (s == "Q") {
string x;
cin >> x;
int xx = mp[x];
int cnt = 0;
vector<int> st;
st.push_back(xx);
while (!st.empty()) {
int u = st.back();
st.pop_back();
if (u <= n) cnt++;
for (int v : g[u]) {
st.push_back(v);
}
}
cout << cnt << endl;
} else break;
}
}
7-13 千手观音
题意:
给定 N 个字符串,每个字符串表示一个数字,数字由若干符号用 '.' 连接而成(类似人类十进制但这里是 10000 进制)。已知这些数字严格递增,需要推断出所有符号的相对大小顺序。如果顺序无法确定,则按字典序升序排列。
思路:
将每个符号看作图中的一个节点。通过比较相邻两个数字,找到第一个不同的符号,建立从前一个符号指向后一个符号的有向边(表示小于关系)。建图后使用拓扑排序,将入度为 0 的节点放入优先队列中按字典序排序,每次取出字典序最小的符号输出,以保证不确定时按字典序排列,再更新相邻点的入度,重复操作。
代码
点击查看代码
vector<string> f(string s){
vector<string>v;
string ss="";
for(int i=0;i<s.length();i++){
if(s[i]=='.'){
v.push_back(ss);
ss="";
continue;
}
ss+=s[i];
}
v.push_back(ss);
return v;
}
vector<int>g[N];
vector<string>ans;
string S[N];
int d[N];
unordered_map<string,int>str;
void solve() {
int n;
cin>>n;
string s;
getline(cin,s);
vector<string> ls;
int p=0;
for(int i=1;i<=n;i++){
getline(cin,s);
vector<string>ss=f(s);
for(auto sss:ss){
if(str.count(sss)==0){
str[sss]=++p;
S[p]=sss;
}
}
if(ss.size()==ls.size()){
int p=0;
while(p<ss.size()&&ss[p]==ls[p]){
p++;
}
if(p<ss.size()){
g[str[ls[p]]].push_back(str[ss[p]]);
d[str[ss[p]]]++;
}
}
ls=ss;
}
priority_queue<string,vector<string>,greater<string>>q;
for(auto [x,y]:str){
if(d[y]==0){
q.push(x);
}
}
while(q.size()){
auto x=q.top();
ans.push_back(x);
q.pop();
for(auto y:g[str[x]]){
d[y]--;
if(d[y]==0){
q.push(S[y]);
}
}
}
int ff=1;
for(auto x:ans){
if(ff){
ff=0;
}else{
cout<<'.';
}
cout<<x;
}cout<<endl;
}
7-14 夺宝大赛
题意:
给定一个 n×m 的地图,0 表示障碍,1 表示可通过,2 表示大本营。有 k 支队伍从不同起点出发,每次可以向四个方向移动一格(耗时 1),不能走障碍。多支队伍同时到达大本营会发生火拼(这些队伍都失败)。求最先到达大本营且不发生火拼的队伍编号及其到达时间。
思路:
从大本营出发 BFS 计算每个可达点到终点的最短距离。然后对于每支队伍,记录其到达时间并按时间排序。找到唯一的最小时间(即前一个和后一个队伍的时间都不同)的队伍即为获胜者。
本题给的是 m 行 n 列,注意行列顺序。
代码
点击查看代码
int a[105][105];
int mn[105][105];
int vis[105][105];
PII b[N];
int xx[4]={0,1,0,-1};
int yy[4]={1,0,-1,0};
void solve() {
int n,m;
cin>>n>>m;
int sx,sy;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
mn[i][j]=1e18;
if(a[i][j]==2){
sx=i;
sy=j;
}
}
}
int k;
cin>>k;
for(int i=1;i<=k;i++){
int x,y;
cin>>y>>x;
b[i]={x,y};
}
priority_queue<PIII,vector<PIII>,greater<PIII>>q;
q.push({0,{sx,sy}});
mn[sx][sy]=0;
while(q.size()){
auto [s,u]=q.top();
auto [x,y]=u;
q.pop();
if(vis[x][y]){
continue;
}
for(int i=0;i<4;i++){
int X=xx[i]+x;
int Y=yy[i]+y;
if(a[X][Y]&&s+1<mn[X][Y]){
mn[X][Y]=s+1;
q.push({s+1,{X,Y}});
}
}
}
vector<PII>ans;
for(int i=1;i<=k;i++){
ans.push_back({mn[b[i].first][b[i].second],i});
}
sort(ans.begin(),ans.end());
for(int i=0;i<ans.size();i++){
if(i&&ans[i].first==ans[i-1].first)continue;
else if(i+1<ans.size()&&ans[i].first==ans[i+1].first)continue;
else if(ans[i].first==1e18)continue;
cout<<ans[i].second<<' '<<ans[i].first<<endl;
return;
}
cout<<"No winner.\n";
}
7-15 喊山
题意:
给定n个山头(编号1-n),m条双向边表示可以互相听到,每个山头最多有2个相邻山头。有k个查询,每个查询给出起点s,要求输出从s出发最远能到达的山头编号。如果有多个最远山头,输出编号最小的。如果s孤立无法到达任何其他山头,输出0。
思路:
对每个查询起点s进行BFS,记录距离和当前最远距离的最小编号节点。使用队列进行BFS遍历,用数组记录距离,同时更新最远距离对应的最小编号节点。
代码
点击查看代码
vector<int>g[N];
int d[N],vis[N];
void solve() {
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
for(int l=1;l<=k;l++){
for(int i=1;i<=n;i++){
vis[i]=0;
d[i]=1e9;
}
int s;
cin>>s;
queue<int>q;
q.push(s);
d[s]=0;
vis[s]=1;
int mx=0,mi=0;
while(q.size()){
auto x=q.front();
// cout<<x<<":";
q.pop();
for(auto y:g[x]){
if(vis[y])continue;
// cout<<y<<' ';
d[y]=d[x]+1;
if(d[y]>mx){
mx=d[y];
mi=y;
}else if(d[y]==mx&&y<mi){
mi=y;
}
vis[y]=1;
q.push(y);
}
// cout<<endl;
}
cout<<mi<<endl;
}
}

浙公网安备 33010602011771号