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 字母串

题意:

给定一个字符串,判断是否合法。规则为:

  1. 当前是大写字母时,下一个必须是:①同字母小写 ②字母表中下一个字母的大写
  2. 当前是小写字母时,下一个必须是:①同字母大写 ②字母表中上一个字母的小写
  3. 字符串可以随时结束

思路:

直接模拟验证每个相邻字符对是否符合规则。注意边界情况:'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) 的数量:

  1. p < q < r,且 p, q, r 都在区间内
  2. 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"。

思路:

  1. 盒子按顺序分组,每组S个盒子对应一批徽章
  2. 每批徽章需要倒序使用(因为栈是后进先出)
  3. 建立盒子编号到徽章类型的直接映射
  4. 查询时直接查找映射表

代码

点击查看代码
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人,用"-"补齐。

思路:

  1. 对每个用户,用map或set去重统计不同标签数量
  2. 按规则排序:先按不同标签数降序,再按总点赞数升序
  3. 输出前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 浪漫侧影

题意

给定二叉树的中序遍历序列和后序遍历序列,要求重建这棵二叉树,然后输出它的右视图和左视图。右视图即为从右侧观察时每层最右边的节点从上到下形成的序列,左视图即为从左侧观察时每层最左边的节点从上到下形成的序列。

思路

  1. 重建二叉树:利用后序遍历的最后一个节点为根节点的特性,在中序遍历序列中找到根节点,从而划分左右子树,递归构建整棵树。
  2. 获取视图
    • 右视图:通过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\) 的路径,满足以下优先级:

  1. 路径总距离最短;
  2. 距离相同时,解放城镇数量最多(经过的节点数);
  3. 以上均相同时,歼敌总数最多(路径上除起点外所有城镇驻军之和)。
    输出唯一满足条件的路径,以及最快路径条数(距离最短的路径数)、最短距离、歼敌总数。

思路:

多约束最短路问题,使用 Dijkstra 算法,同时维护四个状态:

  • \(dist[u]\)\(S\)\(u\) 的最短距离;
  • \(cnt[u]\)\(S\)\(u\) 的最短路径条数;
  • \(city[u]\)\(S\)\(u\) 的最多解放城镇数;
  • \(kill[u]\)\(S\)\(u\) 的最大歼敌数。
    更新时:
  1. \(dist[v] > dist[u] + w\),更新所有状态,\(cnt[v] = cnt[u]\)
  2. \(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 可怜的简单题

题意

我们有一个随机过程:

  1. 初始数列 \(A\) 为空。
  2. 从区间 \([1, n]\) 中均匀随机选择一个整数 \(i\) 加入 \(A\)
  3. 如果存在大于 1 的正整数 \(w\) 使得 \(A\) 中所有元素都是 \(w\) 的倍数(即 \(\gcd(A) > 1\)),则返回步骤 2 继续添加元素。
  4. 否则(\(\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;
}


posted @ 2026-03-17 16:00  ptlks  阅读(182)  评论(0)    收藏  举报