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;
}
posted @ 2026-03-21 17:43  ptlks  阅读(355)  评论(0)    收藏  举报