2026成信大天梯赛题解
不一定保对,我翻的历史代码,不过大概思路是对的。
L1-3
题意:
原题面我忘了,但题面可以等价成爬墙问题。
给定三个正整数 \(s\), \(m\), \(n\),分别表示墙的高度、白天能向上爬的高度、晚上会滑下的高度。每天白天爬升 \(m\),如果达到或超过 \(s\) 则成功爬出;否则晚上滑下 \(n\)。求爬出所需的天数。如果第一天就能爬出(即 \(s \le m\)),输出 \(1\);如果永远无法爬出(即 \(m \le n\)),输出 WanMei!;否则输出计算出的最小天数。
思路:
- 当 \(s \le m\) 时,第一天即可爬出,答案为 \(1\)。
- 当 \(m \le n\) 时,每天净爬升非正,永远无法达到 \(s\),输出 "WanMei!"。
- 否则,设需要 \(x\) 天,则前 \(x-1\) 天每天净爬升 \(m-n\),最后一天爬 \(m\) 且不滑下。因此满足不等式:\((x-1)(m-n) + m \ge s\)。解得 \(x = \left\lceil \frac{s-m}{m-n} \right\rceil + 1\)。利用整数除法向上取整转化为 \(\left\lfloor \frac{s-n-1}{m-n} \right\rfloor + 1\)。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
void solve(){
int s,m,n;
cin>>s>>m>>n;
if(s<=m){
cout<<1<<endl;
return;
}
if(m<=n){
cout<<"WanMei!\n";
return;
}
cout<<(s-m+m-n-1)/(m-n)+1<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L1-4
题意:
给定两个长度为 \(n\) 的数组 \(a\) 和 \(b\),统计满足 \(a_i = b_i\) 的位置数量 \(s\)。如果 \(s = n\),输出 The Fool of Tarot ak!;如果 \(s = 0\),输出 The Fool of Tarot over!;如果 \(s \ge \lfloor n/2 \rfloor\),输出 The Fool of Tarot Okay!;否则输出 The Fool of Tarot so-so!。
思路:
直接遍历数组比较对应元素,计数相等的位置数,然后根据条件判断输出即可。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
int a[N],b[N];
void solve(){
int s=0,m,n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
s+=a[i]==b[i];
}
if(s==n){
cout<<"The Fool of Tarot ak!\n";
}else if(s==0){
cout<<"The Fool of Tarot over!\n";
}else if(s>=n/2){
cout<<"The Fool of Tarot Okay!\n";
}else{
cout<<"The Fool of Tarot so-so!\n";
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
return 0;
}
L1-5
题意:
给定一个正整数 \(n\) \((1 \le n \le 10^5)\),求 \(n\) 的所有真因子(即小于 \(n\) 的因子)中,不是质数的因子之和。
思路:
先用埃氏筛预处理出 \(1\) 到 \(n\) 中每个数是否为非质数(\(1\) 也不是质数)。然后枚举 \(n\) 的所有因子 \(i\) \((1 \le i \le \sqrt{n})\),若 \(i\) 是非质数则加入答案;若对应的另一个因子 \(n/i\) 不等于 \(i\) 且是非质数,也加入答案。注意 \(n\) 本身不计入。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
int a[N],b[N];
void solve(){
int s=0,m,n;
cin>>n;
a[1]=1;
for(int i=2;i<=n;i++){
for(int j=i*2;j<=n;j+=i){
a[j]=1;
}
}
int ans=0;
for(int i=1;i*i<=n;i++){
if(n%i==0){
if(a[i])ans+=i;
if(i*i!=n&&i!=1&&a[n/i])ans+=n/i;
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L1-6
题意:
给定一个日期,格式为 y-m-d。若该日期恰好是 1349-6-28,输出 jiu shi today.;若该日期在 1349-6-28 之前,输出 guo qv le X day?,其中 X 是该日期到目标日期的天数;若在之后,输出 hai cha X day!,其中 X 是到目标日期的剩余天数。
思路:
模拟日期加减。判断给定日期与目标日期的前后关系,然后逐天调整并计数。闰年判断:年份能被4整除且不能被100整除,或能被400整除。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int mn[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
void solve(){
int y,m,d;
char x;
cin>>y>>x>>m>>x>>d;
if(y==1349&&m==6&&d==28){
cout<<"jiu shi today.\n";
return;
}
if(y<1349||(y==1349&&m<6)||(y==1349&&m==6&&d<28)){
int p=0;
while(!(y==1349&&m==6&&d==28)){
if((y%4==0&&y%100!=0)||y%400==0){
mn[2]=29;
}else{
mn[2]=28;
}
p++;
d++;
if(d>mn[m]){
d=1;
m++;
if(m>12){
m=1;
y++;
}
}
}
cout<<"guo qv le "<<p<<" day?"<<endl;
}else{
int p=0;
while(!(y==1349&&m==6&&d==28)){
if((y%4==0&&y%100!=0)||y%400==0){
mn[2]=29;
}else{
mn[2]=28;
}
p++;
d--;
if(d==0){
m--;
if(m==0){
m=12;
y--;
}
d=mn[m];
}
}
cout<<"hai cha "<<p<<" day!"<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L1-7
题意:
给定一个 \(n \times m\) 的矩阵,进行 \(k\) 次操作。每次操作选择当前矩阵中最大的元素(如果有多个任意选一个),然后删除该元素所在的行和列。操作结束后,输出剩余矩阵的所有元素(保持原始行和列的顺序)。
思路:
使用优先队列存储所有元素(值,行,列),按值降序排列。每次取出堆顶元素,若其所在行或列已被删除则跳过,否则标记该行和列为已删除。重复 \(k\) 次。最后遍历原矩阵,只输出未被删除的行和列上的元素。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<int,PII>
#define endl '\n'
using namespace std;
const int N=1e3+10;
int a[N][N];
int H[N],L[N];
void solve(){
int n,m,k;
cin>>n>>m>>k;
priority_queue<PIII,vector<PIII>,less<PIII>>q;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
q.push({a[i][j],{i,j}});
}
}
while(k--&&q.size()){
auto u=q.top();
q.pop();
auto [s,v]=u;
auto [x,y]=v;
// cout<<x<<' '<<y<<endl;
H[x]=1;
L[y]=1;
while(H[q.top().second.first]||L[q.top().second.second]){
q.pop();
}
}
for(int i=1;i<=n;i++){
if(H[i]){
continue;
}
int f=1;
for(int j=1;j<=m;j++){
if(L[j])continue;
if(f){
f=0;
}else{
cout<<' ';
}
cout<<a[i][j];
}cout<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L1-8
题意:
给定n个参赛者,每个参赛者有m道题的得分(-1表示未知,否则为已知得分),每道题最高分为k。同时给出每个参赛者的评分。要求是否存在一种给未知得分赋值的方案(每个未知得分在[0,k]之间),使得评分高的参与者总分一定高,评分相同则无限制。若存在,输出具体的得分方案。
思路:
对于每个参赛者i,可以计算出总分的最小值L[i](已知得分之和)和最大值R[i](将未知得分视为k)。我们按评分a[i]从小到大排序处理。对于当前参赛者i,必须确保其总分至少比所有a值更小的参赛者的最大总分还要大1(这样才能保证严格更多人数高于他们)。用map记录每个a值对应的最大总分。若当前参赛者的R[i]无法达到要求,则无解。否则取max(L[i], 要求值)作为总分,然后贪心分配未知题目的得分(先尽可能分配k,最后剩余不足k的分配给最后一题)。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,PII>
#define endl '\n'
using namespace std;
const int N=4e5+10;
int a[N];
vector<int>g[N];
int L[N],R[N];
PII q[N];
int ans[N];
void solve(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
g[i].clear();
L[i]=R[i]=ans[i]=0;
q[i]={0,0};
cin>>a[i];
int l=0,r=0;
for(int j=1;j<=m;j++){
int x;
cin>>x;
g[i].push_back(x);
if(x==-1){
r+=k;
}else{
L[i]+=x;
}
}
R[i]=L[i]+r;
q[i]={a[i],i};
}
sort(q+1,q+1+n);
int ls=0;
map<int,int>mp;
int f=1;
for(int i=1;i<=n;i++){
auto [s,x]=q[i];
if(i==1){
ans[x]=L[x];
}else{
if(q[i].first!=q[i-1].first){
ls=q[i-1].first;
}
if(R[x]<mp[ls]+1){
f=0;
}else{
ans[x]=max(L[x],mp[ls]+1);
}
}
mp[s]=max(mp[s],ans[x]);
}
if(!f){
cout<<"No\n";
return;
}
cout<<"Yes\n";
for(int i=1;i<=n;i++){
int s=ans[i]-L[i];
for(int j=0;j<m;j++){
if(g[i][j]==-1){
if(s>=k){
s-=k;
g[i][j]=k;
}else{
g[i][j]=s;
s=0;
}
}
cout<<g[i][j]<<" \n"[j==m-1];
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
return 0;
}
L2-1
题意:
给定一个图,有 \(n\) 个点,有 \(n\) 条边,每条边连接两个顶点并有一个编号(从 \(1\) 到 \(n\))。求环上边的最大编号。
思路:
使用拓扑排序移除所有不在环上的节点。将所有度数为 \(1\) 的节点入队,然后进行 BFS:移除队首节点,减少其邻居的度数。若邻居度数变为 \(1\),则入队。最后未被移除的节点即在环上。遍历这些节点的所有边,找到编号最大的边即为答案。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,PII>
#define endl '\n'
using namespace std;
const int N=4e5+10;
int a[N];
vector<PII>g[N];
int d[N];
int vis[N];
void solve(){
int n,m,k;
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
g[x].push_back({y,i});
g[y].push_back({x,i});
d[x]++;
d[y]++;
}
queue<int>q;
for(int i=1;i<=n;i++){
if(d[i]==1){
q.push(i);
}
}
while(q.size()){
auto x=q.front();
q.pop();
vis[x]=1;
for(auto [y,i]:g[x]){
if(vis[y])continue;
d[y]--;
if(d[y]==1){
q.push(y);
}
}
}
int ans=0;
for(int x=1;x<=n;x++){
if(vis[x])continue;
for(auto [y,i]:g[x]){
if(vis[y])continue;
ans=max(ans,i);
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L2-2
题意:
有 \(n\) 个物品围成一圈,第 \(i\) 个物品有一个权值 \(a_i\)。每次操作选择两个相邻物品,将其合并,合并代价为较大权值,合并后的权值为较大权值。重复操作直到只剩一个物品,求总代价的最小值。
思路:
贪心策略:每次选取当前权值最小的物品,将其与相邻的两个物品中权值较小的那个合并。这样每次合并的代价是相邻较小权值,可以保证总体代价尽量小。使用优先队列维护最小权值物品,并用双向链表维护相邻关系,动态更新环结构。
时间复杂度:\(O(n \log n)\)。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,PII>
#define endl '\n'
using namespace std;
const int N=4e5+10;
int a[N];
int L[N],R[N];
vector<PII>g[N];
int d[N];
int vis[N];
void solve(){
int n,m,k;
cin>>n;
priority_queue<PII,vector<PII>,greater<PII>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
q.push({a[i],i});
if(i==1)L[i]=n;
else L[i]=i-1;
if(i==n)R[i]=1;
else R[i]=i+1;
}
int ans=0;
while(q.size()>1){
auto [x,i]=q.top();
q.pop();
int l=a[L[i]];
int r=a[R[i]];
if(l>=r){
ans+=r;
}else{
ans+=l;
}
R[L[i]]=R[i];
L[R[i]]=L[i];
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
return 0;
}
L2-3
题意:
给定一棵有 \(n\) 个节点的树,求一个节点 \(v\),使得所有节点到 \(v\) 的距离之和最小,并输出这个节点和最小距离和。
思路:
使用换根 DP。第一次 DFS 计算每个节点的子树大小和子树内所有节点到该节点的距离和。第二次 DFS 进行换根,对于每个节点,计算它作为根时的总距离和,并更新最小值。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,PII>
#define endl '\n'
using namespace std;
const int N=4e5+10;
int a[N];
int sum[N],ans[N];
vector<int>g[N];
void dfs(int x,int fa){
sum[x]=1;
ans[x]=0;
for(auto u:g[x]){
if(u!=fa){
dfs(u,x);
sum[x]+=sum[u];
ans[x]+=ans[u]+sum[u];
}
}
}
int mx=1e9,mi=0;
void dfs1(int x,int fa){
if(ans[x]<mx){
mx=ans[x];
mi=x;
}
for(auto u:g[x]){
if(u!=fa){
ans[x]-=ans[u]+sum[u];
sum[x]-=sum[u];
sum[u]+=sum[x];
ans[u]+=ans[x]+sum[x];
dfs1(u,x);
ans[u]-=ans[x]+sum[x];
sum[u]-=sum[x];
sum[x]+=sum[u];
ans[x]+=ans[u]+sum[u];
}
}
}
void solve(){
int n,m,k;
cin>>n;
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);
dfs1(1,0);
cout<<mi<<' '<<mx<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L2-4
题意:
给定一个无向图,有 \(n\) 个顶点和 \(m\) 条边,每个顶点有权值 \(a_i\)。给定起点 \(s\) 和终点 \(d\)。定义路径长度为边权之和,路径点权和为路径上所有顶点权值之和。求从 \(s\) 到 \(d\) 的最短路径数量,以及在所有最短路径中,点权之和的最大值。
思路:
使用 Dijkstra 算法计算最短距离。对每个点维护最短距离、该距离下的最大点权和、最短路径数量。若找到更短距离,则更新距离和点权和,路径数设为前驱的路径数;若距离相等,则更新点权和为最大值,并将当前路径数加上前驱的路径数。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,int>
#define endl '\n'
using namespace std;
const int N=4e5+10;
int a[N];
int sum[N],vis[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];
q.push({{-ss1,ss2},y});
}else if(ss1==mn[y].first){
sum[y]+=sum[x];
if(ss2>mn[y].second){
mn[y]={ss1,ss2};
q.push({{-ss1,ss2},y});
}
}
}
}
cout<<sum[d]<<" "<<mn[d].second<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L3-1
题意:
给定一个无向图,有 \(n\) 个节点和 \(m\) 条边,每条边有一个正整数权重。求从节点 \(1\) 到节点 \(n\) 的哈密顿路径(即经过所有节点恰好一次)的最小权重和 \(c\)。然后给定一个整数 \(h\),如果 \(c < h\),输出 Yes 和 \(h - c\);否则输出 No 和 \(c - h + 1\)。
思路:
\(n \le 20\),考虑状压 DP。令 \(dp[i][mask]\) 表示已访问节点集合为 \(mask\)(\(mask\) 的第 \(j\) 位为 \(1\) 表示节点 \(j+1\) 已访问),当前位于节点 \(i\)的最小权重和。初始化 \(dp[1][1] = 0\),通过dfs去遍历每个未访问的节点 \(j\),通过边 \((i,j)\) 更新 \(dp[j][mask | (1<<(j-1))]\)。最终 \(dp[n][(1<<n)-1]\) 即为 \(c\),按题意输出即可。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,int>
#define endl '\n'
using namespace std;
const int N=4e5+10;
int a[N];
int sum[N],vis[N];
vector<PII>g[N];
int mn[20][1ll<<20];
int n,m,h;
void dfs(int x,int s,int sum){
// cout<<x<<' '<<s<<' '<<sum<<endl;
mn[x][s]=sum;
for(auto [y,w]:g[x]){
if(y==n&&s!=(1ll<<(n-1))-1)continue;
if(s>>(y-1)&1){
}else{
if(sum+w<mn[y][s|(1ll<<(y-1))]){
dfs(y,s|(1ll<<(y-1)),sum+w);
}
}
}
}
void solve(){
cin>>n>>m>>h;
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});
}
for(int i=0;i<(1ll<<n);i++){
for(int j=1;j<=n;j++){
mn[j][i]=1e18;
}
}
dfs(1,(1ll<<(1-1)),0);
if(mn[n][(1ll<<n)-1]<h){
cout<<"Yes\n";
cout<<h-mn[n][(1ll<<n)-1]<<endl;
}else{
cout<<"No\n";
cout<<mn[n][(1ll<<n)-1]-h+1<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}
L3-2
题意:
给定一个长度为 \(n\) 的字符串 \(s\),每次操作可以删除字符串中的任意一个字符。求最少需要多少次操作,可以使剩下的字符串成为一个回文串。
思路:
使用动态规划。定义 \(dp[l][r]\) 表示将子串 \(s[l \ldots r]\) 变成回文串所需的最少删除次数。
- 当 \(l \geq r\) 时,\(dp[l][r] = 0\)(空串或单字符已经是回文)。
- 当 \(s[l] == s[r]\) 时,两端字符相同,可以直接考虑内部子串,即 \(dp[l][r] = dp[l+1][r-1]\)。
- 否则,需要删除左端或右端字符,即 \(dp[l][r] = \min(dp[l+1][r], dp[l][r-1]) + 1\)。
从小到大枚举子串长度计算 \(dp\) 值,最终答案即为 \(dp[1][n]\)。
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
#define PIII pair<PII,int>
#define endl '\n'
using namespace std;
const int N=5e3+10;
int n,m,h;
string s,t;
int ans[N][N];
void solve(){
cin>>n;
cin>>s;
s='0'+s;
for(int len=1;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
ans[l][r]=1e9;
if(s[l]==s[r]){
ans[l][r]=min(ans[l][r],ans[l+1][r-1]);
}
ans[l][r]=min(ans[l][r],ans[l][r-1]+1);
ans[l][r]=min(ans[l][r],ans[l+1][r]+1);
}
}
cout<<ans[1][n]<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
return 0;
}

浙公网安备 33010602011771号