SMU 2026 Spring 天梯赛4题解
L1-5 不变初心数
题意:
给定多个正整数,判断每个数是否是不变初心数。不变初心数定义为:将这个数分别乘以 2,3,4,5,6,7,8,9 时,所得乘积的各位数字之和保持不变。如果满足条件,输出这个保持不变的和(即初心);否则输出 NO。
思路:
直接模拟计算即可。对于每个数 x,先计算 2x 的各位数字之和作为基准值,然后验证 3x 到 9x 的各位数字之和是否都等于这个基准值。计算各位数字之和可以用循环取模累加。
代码
点击查看代码
int ff(int x){
int s=0;
while(x){
s+=x%10;
x/=10;
}
return s;
}
void solve() {
int t;
cin>>t;
int s=ff(2*t);
for(int i=3;i<=9;i++){
if(ff(t*i)!=s){
cout<<"NO\n";
return;
}
}
cout<<s<<endl;
}
L1-6 字母串
题意:
给定一个字符串,判断是否合法。规则为:
- 当前是大写字母时,下一个必须是:①同字母小写 ②字母表中下一个字母的大写
- 当前是小写字母时,下一个必须是:①同字母大写 ②字母表中上一个字母的小写
- 字符串可以随时结束
思路:
直接模拟验证每个相邻字符对是否符合规则。注意边界情况:'a'没有前一个字母,'Z'没有下一个字母。
代码
点击查看代码
void solve() {
string s;
cin>>s;
int n=s.length();
int f=1;
for(int i=0;i+1<n;i++){
if(s[i]>='a'&&s[i]<='z'){
if((s[i+1]>='A'&&s[1+i]<='Z')&&s[i+1]-'A'+'a'==s[i]){
}else if((s[i+1]>='a'&&s[1+i]<='z')&&s[i+1]+1==s[i]){
}else{
f=0;
}
}else{
if((s[i+1]>='a'&&s[1+i]<='z')&&s[i+1]-'a'+'A'==s[i]){
}else if((s[i+1]>='A'&&s[1+i]<='Z')&&s[i+1]==s[i]+1){
}else{
f=0;
}
}
}
cout<<"NY"[f]<<endl;
}
L1-7 矩阵列平移
题意:
给定一个 n×n 的矩阵,将偶数列依次向下平移 1, 2, ..., k, 1, 2, ..., k, ... 个位置,平移空出的位置用数字 x 填充。计算新矩阵每行的和。
思路:
模拟平移过程:对于每个偶数列 j(从第 2 列开始),计算它应该向下平移的步数 step = ((j/2 - 1) % k) + 1。那么新矩阵中第 i 行第 j 列的值应为:
- 如果 i > step,则为 a[i-step][j]
- 如果 i ≤ step,则为 x
奇数列保持不变。计算每行的和并输出。
代码
点击查看代码
int a[105][105];
void solve() {
int n,k,m;
cin>>n>>k>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
int s=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(j%2==0){
int ii=i-((j/2-1)%k+1);
if(ii<=0){
s+=m;
}else{
s+=a[ii][j];
}
}else{
s+=a[i][j];
}
}
cout<<s<<" \n"[i==n];
s=0;
}
}
L1-8 均是素数
题意:
给定区间 [m, n] (0 < m < n ≤ 1000),求满足以下条件的素数三元组 (p, q, r) 的数量:
- p < q < r,且 p, q, r 都在区间内
- pq + r, qr + p, rp + q 都是素数
思路:
先用欧拉筛筛出范围内的素数,然后枚举所有三元组判断条件。
代码
点击查看代码
int flg[N];
void init() {
flg[0] = flg[1] = 1;
for (int i = 2; i < N; ++i) {
if (!flg[i]) {
for (int j = i + i; j < N; j += i) {
flg[j] = 1;
}
}
}
}
void solve() {
init();
int l, r;
cin >> l >> r;
vector<int> p;
for (int i = l; i <= r; ++i) {
if (!flg[i]) {
p.push_back(i);
}
}
int n = p.size();
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
for (int k = j + 1; k < n; ++k) {
int a = p[i], b = p[j], c = p[k];
if (!flg[a * b + c] && !flg[b * c + a] && !flg[c * a + b]) {
++ans;
}
}
}
}
cout << ans << endl;
}
L2-1 盲盒包装流水线
题意:
给定N个盒子的编号顺序(进入流水线的顺序)和N/S批徽章类型(每批S个,按入栈顺序给出)。流水线工作方式:货栈容量S,每批徽章入栈后,按照栈的特性(后进先出)依次装入当前批次对应的S个盒子中。之后会有K次查询,每次查询一个盒子编号,需要输出该盒子中装的徽章类型,如果编号不存在则输出"Wrong Number"。
思路:
- 盒子按顺序分组,每组S个盒子对应一批徽章
- 每批徽章需要倒序使用(因为栈是后进先出)
- 建立盒子编号到徽章类型的直接映射
- 查询时直接查找映射表
代码
点击查看代码
int a[N];
int b[N];
void solve() {
int n,s;
cin>>n>>s;
for(int i=1;i<=n;i++){
int x;
cin>>x;
a[x]=i;
}
for(int i=1;i<=n/s;i++){
for(int j=s;j>=1;j--){
int x;
cin>>x;
b[(i-1)*s+j]=x;
}
}
int m;
cin>>m;
for(int i=1;i<=m;i++){
int x;
cin>>x;
if(a[x]){
cout<<b[a[x]]<<endl;
}else{
cout<<"Wrong Number\n";
}
}
}
L2-2 点赞狂魔
题意:
统计N个用户的点赞情况,每个用户有用户名和K个标签编号,可能有重复标签。计算每个用户点赞的不同标签数量,找出前3名。排名规则:优先按不同标签数量降序,如果相同则按标签出现次数平均值(总点赞数/不同标签数)升序(即总点赞数少者优先)。若不足3人,用"-"补齐。
思路:
- 对每个用户,用map或set去重统计不同标签数量
- 按规则排序:先按不同标签数降序,再按总点赞数升序
- 输出前3名,不足用"-"补全
代码
点击查看代码
struct asd{
int n,s;
string a;
}a[N];
bool cmp(asd x,asd y){
if(x.n!=y.n){
return x.n>y.n;
}else{
return x.s*y.n<y.s*x.n;
}
}
void solve() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].a;
int k;
cin>>k;
map<int,int>q;
for(int j=1;j<=k;j++){
int x;
cin>>x;
q[x]++;
a[i].s++;
}
a[i].n=q.size();
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=3;i++){
if(i>n)cout<<'-';
else cout<<a[i].a;
cout<<" \n"[i==3];
}
}
L2-3 浪漫侧影
题意
给定二叉树的中序遍历序列和后序遍历序列,要求重建这棵二叉树,然后输出它的右视图和左视图。右视图即为从右侧观察时每层最右边的节点从上到下形成的序列,左视图即为从左侧观察时每层最左边的节点从上到下形成的序列。
思路
- 重建二叉树:利用后序遍历的最后一个节点为根节点的特性,在中序遍历序列中找到根节点,从而划分左右子树,递归构建整棵树。
- 获取视图:
- 右视图:通过DFS遍历,按先左后右的顺序访问子节点,每层最后一个访问的节点即为该层最右节点。
- 左视图:通过DFS遍历,按先右后左的顺序访问子节点,每层最后一个访问的节点即为该层最左节点。
代码
点击查看代码
vector<int>g[N];
int a[N];
int b[N];
int ans=0;
int dfs(int l1,int r1,int l2,int r2){
if(l1>r1||l2>r2)return -1;
int rt=b[r2];
// cout<<l1<<' '<<l2<<' '<<rt<<endl;
int L=l1-1;
for(int i=l1;i<=r1;i++){
if(a[i]==rt){
L=i-1;
break;
}
}
int l=dfs(l1,L,l2,l2+(L-l1));
int r=dfs(L+2,r1,l2+(L-l1)+1,r2-1);
if(l!=-1)g[rt].push_back(l);
if(r!=-1)g[rt].push_back(r);
return rt;
}
int mx=0;
void dfs1(int x,int s){
// cout<<x<<' '<<s<<endl;
a[s]=x;
mx=max(mx,s);
for(auto u:g[x]){
dfs1(u,s+1);
}
}
void dfs2(int x,int s){
a[s]=x;
mx=max(mx,s);
for(int i=(int)g[x].size()-1;i>=0;i--){
int u=g[x][i];
dfs2(u,s+1);
}
}
void solve() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
int rt=dfs(1,n,1,n);
// for(int i=1;i<=n;i++){
// cout<<i<<':';
// for(auto x:g[i]){
// cout<<x<<' ';
// }cout<<endl;
// }
dfs1(rt,1);
cout<<"R: ";
for(int i=1;i<=mx;i++){
cout<<a[i]<<" \n"[i==mx];
}
dfs2(rt,1);
cout<<"L: ";
for(int i=1;i<=mx;i++){
cout<<a[i]<<" \n"[i==mx];
}
}
L2-4 哲哲打游戏
题意:
游戏有 N 个剧情点,每个剧情点有若干选项,选择后前往对应剧情点。进行 M 次操作:0 j 表示做出当前剧情点的第 j 个选择并移动;1 j 表示存档到第 j 档位;2 j 表示读取第 j 档位存档。初始在剧情点 1。对每个存档操作输出当前剧情点编号,最后输出最终剧情点编号。
思路:
直接模拟即可。用 vector 数组存每个剧情点的选项,用数组存档位。按操作类型更新当前位置。
代码
点击查看代码
vector<int>g[N];
int a[N];
int b[N];
void solve() {
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int k;
cin>>k;
for(int j=1;j<=k;j++){
int x;
cin>>x;
g[i].push_back(x);
}
}
int p=1;
while(m--){
int op,x;
cin>>op>>x;
if(op==0){
p=g[p][x-1];
}else if(op==1){
b[x]=p;
cout<<p<<endl;
}else{
p=b[x];
}
}
cout<<p<<endl;
}
L3-1 直捣黄龙
题意:
给定 \(N\) 个城镇(代号为3个大写字母),\(K\) 条双向道路及其长度,己方大本营 \(S\) 和敌方大本营 \(T\),以及每个城镇的驻军数量。要求找到从 \(S\) 到 \(T\) 的路径,满足以下优先级:
- 路径总距离最短;
- 距离相同时,解放城镇数量最多(经过的节点数);
- 以上均相同时,歼敌总数最多(路径上除起点外所有城镇驻军之和)。
输出唯一满足条件的路径,以及最快路径条数(距离最短的路径数)、最短距离、歼敌总数。
思路:
多约束最短路问题,使用 Dijkstra 算法,同时维护四个状态:
- \(dist[u]\):\(S\) 到 \(u\) 的最短距离;
- \(cnt[u]\):\(S\) 到 \(u\) 的最短路径条数;
- \(city[u]\):\(S\) 到 \(u\) 的最多解放城镇数;
- \(kill[u]\):\(S\) 到 \(u\) 的最大歼敌数。
更新时:
- 若 \(dist[v] > dist[u] + w\),更新所有状态,\(cnt[v] = cnt[u]\);
- 若 \(dist[v] = dist[u] + w\),\(cnt[v] += cnt[u]\),再根据 \(city\) 和 \(kill\) 决定是否更新路径。
因为 \(N \le 200\),用 \(map\) 映射字符串编号即可。
代码
点击查看代码
vector<PII> g[N];
int a[N],d[N],c[N],k[N],cnt[N],pre[N];
string s[N];
map<string,int> mp;
void solve(){
int n,m;
string sS,sT;
cin>>n>>m>>sS>>sT;
mp[sS]=1;
s[1]=sS;
for(int i=2;i<=n;i++){
cin>>s[i]>>a[i];
mp[s[i]]=i;
}
for(int i=0;i<m;i++){
string u,v;
int w;
cin>>u>>v>>w;
int x=mp[u],y=mp[v];
g[x].push_back({y,w});
g[y].push_back({x,w});
}
int S=mp[sS],T=mp[sT];
for(int i=1;i<=n;i++) d[i]=INF;
d[S]=0;
c[S]=1;
cnt[S]=1;
priority_queue<PII,vector<PII>,greater<PII>> pq;
pq.push({0,S});
while(!pq.empty()){
auto [dd,u]=pq.top();
pq.pop();
if(dd>d[u]) continue;
for(auto [v,w]:g[u]){
if(d[v]>d[u]+w){
d[v]=d[u]+w;
c[v]=c[u]+1;
k[v]=k[u]+a[v];
cnt[v]=cnt[u];
pre[v]=u;
pq.push({d[v],v});
}else if(d[v]==d[u]+w){
cnt[v]+=cnt[u];
if(c[v]<c[u]+1){
c[v]=c[u]+1;
k[v]=k[u]+a[v];
pre[v]=u;
}else if(c[v]==c[u]+1 && k[v]<k[u]+a[v]){
k[v]=k[u]+a[v];
pre[v]=u;
}
}
}
}
vector<int> path;
for(int u=T;u!=S;u=pre[u]) path.push_back(u);
path.push_back(S);
reverse(path.begin(),path.end());
for(int i=0;i<path.size();i++){
if(i) cout<<"->";
cout<<s[path[i]];
}
cout<<endl;
cout<<cnt[T]<<' '<<d[T]<<' '<<k[T]<<endl;
}
L3-2 拼题A打卡奖励
题意:
给定 N 张打卡卷,每张需要 m_i 分钟完成,可获得 c_i 枚金币。活动总时长为 M 分钟,每张卷最多做一次,求在不超过 M 分钟的情况下最多能获得多少金币。
思路:
由于 M 可能很大(最大为一年525600分钟),但金币数量 c_i ≤ 30,总金币数不超过 30000。因此可以将经典背包问题转换思路:dp[j] 表示获得恰好 j 枚金币所需的最小时间。初始化 dp[0] = 0,其余为无穷大。对于每张卷子 (m_i, c_i),从总金币数向下更新 dp[j] = min(dp[j], dp[j - c_i] + m_i)。最后从大到小查找第一个 dp[j] ≤ M 的 j 即为答案。
代码
点击查看代码
int dp[30010];
void solve() {
int n,m;
cin>>n>>m;
vector<int> t(n+1),c(n+1);
for(int i=1;i<=n;i++) cin>>t[i];
for(int i=1;i<=n;i++) cin>>c[i];
int sum=0;
for(int i=1;i<=n;i++) sum+=c[i];
for(int i=1;i<=sum;i++) dp[i]=INF;
dp[0]=0;
for(int i=1;i<=n;i++){
for(int j=sum;j>=c[i];j--){
dp[j]=min(dp[j],dp[j-c[i]]+t[i]);
}
}
int ans=0;
for(int j=sum;j>=0;j--){
if(dp[j]<=m){
ans=j;
break;
}
}
cout<<ans<<endl;
}
L3-3 可怜的简单题
题意
我们有一个随机过程:
- 初始数列 \(A\) 为空。
- 从区间 \([1, n]\) 中均匀随机选择一个整数 \(i\) 加入 \(A\)。
- 如果存在大于 1 的正整数 \(w\) 使得 \(A\) 中所有元素都是 \(w\) 的倍数(即 \(\gcd(A) > 1\)),则返回步骤 2 继续添加元素。
- 否则(\(\gcd(A) = 1\)),停止并返回 \(A\)。
求停止时数列 \(A\) 的期望长度,结果对质数 \(p\) 取模(\(p > n\))。
思路
设 \(f(d,x)\) 为序列的前 \(x\) 位 \(gcd\) 被 \(d\) 整除的概率。
设 \(g(d,x)\) 为序列的前 \(x\) 位 \(gcd\) 为 \(d\) 的概率。
不难得出 \(f(d,x)=(\frac{\lfloor \frac{n}{d}\rfloor}{n})^x.\)
\(\displaystyle f(d,x)=\sum_{d|k}g(k,x).\)
\(\iff\displaystyle g(d,x)=\sum_{d|k}\mu(\frac{k}{d})\cdot f(k,x).\)
\(\displaystyle g(1,x)=\sum^{n}_{k=1}\mu(k)\cdot f(k,x)=\sum^{n}_{k=1}\mu(k)\cdot(\frac{\lfloor \frac{n}{k}\rfloor}{n})^x.\)
那序列长度大于 \(x\) 的概率为 \(\displaystyle P(x)=1-g(1,x)=1-\sum^{n}_{k=1}\mu(k)\cdot(\frac{\lfloor \frac{n}{k}\rfloor}{n})^x\)
\(\displaystyle =-\sum^{n}_{k=2}\mu(k)\cdot(\frac{\lfloor \frac{n}{k}\rfloor}{n})^x\)
则序列的期望长度为
\(\displaystyle E=\sum^{\infty}_{x=0}P(x)=1-\sum^{\infty}_{x=1}\sum^{n}_{k=2}\mu(k)\cdot(\frac{\lfloor \frac{n}{k}\rfloor}{n})^x\)
\(\displaystyle =1-\sum^{n}_{k=2}\mu(k)\sum^{\infty}_{x=1}(\frac{\lfloor \frac{n}{k}\rfloor}{n})^x=1 -\sum^{n}_{k=2}\mu(k)(\frac{\lfloor \frac{n}{k}\rfloor}{n-\lfloor \frac{n}{k}\rfloor})\)
用数论分块和杜教筛即可求解。
代码
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define double long double
#define PII pair<int,int>
#define PIV pair<int,vector<int>>
#define PIII pair<int,PII>
#define LL __int128
const int N = 2e6 + 10, INF = 1e18;
int mod = 998244353;
const int M = 4e2 + 10;
const double eps = 1e-6;
LL qpow(LL x, int n) {
LL ans = 1;
while (n) {
if (n & 1) {
ans *= x;
ans %= mod;
}
x *= x;
x %= mod;
n >>= 1;
}
return ans;
}
int fac[N], inv[N];
void fact(int K) {
fac[0] = 1;
for (int i = 1; i <= K; ++i) fac[i] = fac[i - 1] * i % mod;
inv[K] = qpow(fac[K], mod - 2);
for (int i = K - 1; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
}
int C(int n, int k) {
if (k < 0 || k > n) return 0;
return fac[n] * inv[k] % mod * inv[n - k] % mod;
}
constexpr int MAXN = 5000010;
long long T, n, pri[MAXN], cur, mu[MAXN], sum_mu[MAXN];
bool vis[MAXN];
map<long long, long long> mp_mu;
long long S_mu(long long x) { // 求mu的前缀和
if (x < MAXN) return sum_mu[x];
if (mp_mu[x]) return mp_mu[x]; // 如果map中已有该大小的mu值,则可直接返回
long long ret = (long long)1;
for (long long i = 2, j; i <= x; i = j + 1) {
j = x / (x / i);
ret -= S_mu(x / i) * (j - i + 1);
}
return mp_mu[x] = ret; // 路径压缩,方便下次计算
}
long long S_phi(long long x) { // 求phi的前缀和
long long ret = (long long)0;
long long j;
for (long long i = 1; i <= x; i = j + 1) {
j = x / (x / i);
ret += (S_mu(j) - S_mu(i - 1)) * (x / i) * (x / i);
}
return (ret - 1) / 2 + 1;
}
void init(){
mu[1] = 1;
for (int i = 2; i < MAXN; i++) { // 线性筛预处理mu数组
if (!vis[i]) {
pri[++cur] = i;
mu[i] = -1;
}
for (int j = 1; j <= cur && i * pri[j] < MAXN; j++) {
vis[i * pri[j]] = true;
if (i % pri[j])
mu[i * pri[j]] = -mu[i];
else {
mu[i * pri[j]] = 0;
break;
}
}
}
for (int i = 1; i < MAXN; i++)
sum_mu[i] = sum_mu[i - 1] + mu[i]; // 求mu数组前缀和
}
int H(int n){
LL res=0;
int l=2,r;
while(l<=n){
r=n/(n/l);
res+=(S_mu(r)-S_mu(l-1)+mod)%mod*((LL)(n/l)*qpow((n-n/l),mod-2)%mod)%mod;
// cout<<l<<' '<<r<<' '<<(S_mu(r)-S_mu(l-1)+mod)%mod<<' '<<((n/l)/(n-n/l))%mod<<endl;
res%=mod;
l=r+1;
}
return res;
}
void solve() {
init();
int n, p;
cin>>n>>mod;
cout<<(mod+1-H(n))%mod<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号