2025—暑期训练一

A

本题描述了一个最优路径规划问题的解法,核心思路是利用数轴上区间覆盖的特性,将问题简化为两个端点的访问问题。以下是关键点的详细解析:

核心观察

  1. 区间覆盖特性

    • 给定的位置数组 x1, x2, ..., xn 是严格递增的(即 x1 < x2 < ... < xn)。
    • 在数轴上,若要访问区间 [x1, xn] 内的所有整数点,只需从起点移动到 x1xn,再移动到另一个端点,即可覆盖中间的所有位置。
  2. 最优路径选择

    • 从起点 s 出发,访问 x1xn 的路径只有两种可能:

      1. 路径 As → x1 → xn
        总步数:|s - x1| + |xn - x1|
      2. 路径 Bs → xn → x1
        总步数:|s - xn| + |xn - x1|
    • 两种路径的共同部分是 |xn - x1|(即区间长度),因此只需比较起点到两个端点的距离,取较小值。

公式推导

最优解的公式为:

复制代码
min(|s - x1|, |s - xn|) + (xn - x1)

解释

  • min(|s - x1|, |s - xn|):选择起点 s 到区间左端点 x1 或右端点 xn 的较短距离。
  • xn - x1:区间的长度,即覆盖整个区间所需的最小步数。

算法实现

该算法的时间复杂度为 O(1),因为只需读取输入并计算两个端点的位置。具体步骤:

  1. 读取输入的起点 s 和位置数组 x
  2. 计算 x1(数组第一个元素)和 xn(数组最后一个元素)。
  3. 代入公式 min(|s - x1|, |s - xn|) + (xn - x1) 计算结果。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
void solve(){
	int n,s;
	cin>>n>>s;
	int a[n+1];
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int l=min(abs(a[1]-s),abs(a[n]-s))+a[n]-a[1];
	cout<<l<<endl;
}
int main(){
	int t;
	cin>>t;
	while(t--)solve();
	return 0;
}

B

简化问题

  • 如果存在满足条件的分割方案,则必然存在一种方案使得中间字符串b的长度为 1。

  • 证明 :假设存在分割s = a + b + c,其中|b| ≥ 1。选择b中的任意字符x,将b拆分为b = b_prefix + x + b_suffix,则新的分割为:

    plaintext

    复制代码
    a' = a + b_prefix  
    b' = x  
    c' = b_suffix + c  

    这种转换不改变分割的有效性,因此只需考虑|b| = 1的情况。

  1. 字符出现次数的影响

    • 统计每个字符在s中出现的次数cnt[l]
    • 若存在某个字符l满足以下条件之一,则存在有效分割:
      1. 条件 1cnt[l] ≥ 3
        选择第二个出现的l作为b,其前后的字符分别构成ac
      2. 条件 2cnt[l] = 2s的首尾字符不全为l
        选择非首尾位置的l作为b,确保ac非空。

算法实现

贪心算法: 通过局部最优选择(优先考虑|b|=1)达到全局最优解,避免枚举所有可能的分割方式。该算法的时间复杂度为 O(n),具体步骤:

  1. 统计字符频率

    遍历字符串s,统计每个字符的出现次数cnt[l]

  2. 检查条件 1

    若存在任何字符l的出现次数≥3,直接返回 "Yes"。

  3. 检查条件 2

    对于每个出现两次的字符l,检查:

    • s的首字符或尾字符不等于l,则返回 "Yes"。
  4. 返回结果

    若所有条件均不满足,返回 "No"。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
void solve(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	int cnt[30]={0};
	for(int i=0;i<s.length();i++){
		cnt[s[i]-'a']++;
	}
	bool flag=0;
	for(int i=0;i<26;i++){
		if(cnt[i]>2)flag=1;
		else if(cnt[i]==2&&(s[0]-'a'!=i||s.back()-'a'!=i))
		flag=1;
	}
	if(flag)cout<<"YES"<<endl;
	else cout<<"NO"<<endl;
}
int main(){
	int t;
	cin>>t;
	while(t--)solve();
	return 0;
}

C

矩阵操作后的最小可能最大值分析

这段文字描述了一个矩阵操作问题的解法,核心思路是通过分析矩阵中最大值的分布,确定执行一次操作后可能的最小最大值。关键点如下:

核心观察

  1. 答案的可能范围

    • 矩阵的初始最大值为 mx。执行一次操作后,答案只能是 mx-1mx
    • 证明 :无论选择哪一行 r 和列 c 进行操作,矩阵中的其他元素不会减少,因此最大值不可能小于 mx-1
  2. 何时答案为 mx-1

    • 当且仅当存在一个位置 (r, c),使得所有值为 mx 的元素都位于第 rc 列时,答案为 mx-1
    • 解释 :选择这样的 (r, c) 进行操作后,所有 mx 元素都会减 1,从而新的最大值为 mx-1

算法实现步骤

  1. 预处理

    • 遍历矩阵,记录:
      • mx:矩阵中的最大值。
      • cnt_mxmx 出现的总次数。
      • r[i]:第 i行中 mx 出现的次数。
      • c[j]:第 j 列中 mx 出现的次
  2. 检查条件

    • 对于每个可能的位置 (i, j),计算:

      cpp 复制代码
      count = r[i] + c[j] - (a[i][j] == mx ? 1 : 0)

      其中,r[i] + c[j] 是第 ri行和第j列中 mx 的总次数,但如果 a[r][c]mx,则被重复计算了一次,需要减去 1。

  3. 判断结果

  • 如果存在 (i,j) 使得 count == cnt_mx,则答案为 mx-1;否则为 mx

错解(数组开的过大):

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=100005;  //数组过大
int a[N][N];
void solve(){
	int n,m;
	cin>>n>>m;
	int mx=0,cnt_mx=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cin>>a[i][j];
			if(a[i][j]>mx){
				mx=a[i][j];
				cnt_mx=1;
			}
			else if(a[i][j]==mx) cnt_mx++;
		}
	}
	int r[n]={0},c[m]={0};
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(a[i][j]==mx){
				r[i]++;
				c[j]++;
			}
		}
	}
	int flag=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(r[i]+c[j]-(a[i][j]==mx?1:0)==cnt_mx) flag=1;
		}
	}
	cout<<mx-flag<<endl;
}
int main(){
	int t;
	cin>>t;
	while(t--)solve();
	return 0;
}

正确代码

根据题目约束 1≤n⋅m≤1e5,建议使用动态分配数组定义数组a,同时行数组和列数组使用变量(n,m)定义数组的大小。

使用vector嵌套来定义动态大小的矩阵

cpp 复制代码
#include <vector>

// 定义n行m列的矩阵,初始值为0
int n, m;
cin >> n >> m;
vector<vector<int>> matrix(n, vector<int>(m, 0));

// 访问元素:matrix[i][j]
matrix[0][0] = 10;  // 第一行第一列赋值为10

解释

  • vector<vector<int>> matrix(n, ...):创建一个包含n个元素的外层 vector,每个元素是一个内层 vector。
  • vector<int>(m, 0):每个内层 vector 包含m个元素,初始值为 0。

总代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
void solve(){
	int n,m;
	cin>>n>>m;
	int mx=0,cnt_mx=0;
	vector<vector<int>>a(n,vector<int>(m));
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cin>>a[i][j];
			if(a[i][j]>mx){
				mx=a[i][j];
				cnt_mx=1;
			}
			else if(a[i][j]==mx) cnt_mx++;
		}
	}
	int r[n]={0},c[m]={0};
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(a[i][j]==mx){
				r[i]++;
				c[j]++;
			}
		}
	}
	int flag=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(r[i]+c[j]-(a[i][j]==mx?1:0)==cnt_mx) flag=1;
		}
	}
	cout<<mx-flag<<endl;
}
int main(){
	int t;
	cin>>t;
	while(t--)solve();
	return 0;
}
相关推荐
Gyoku Mint23 分钟前
深度学习×第4卷:Pytorch实战——她第一次用张量去拟合你的轨迹
人工智能·pytorch·python·深度学习·神经网络·算法·聚类
葫三生1 小时前
如何评价《论三生原理》在科技界的地位?
人工智能·算法·机器学习·数学建模·量子计算
拓端研究室4 小时前
视频讲解:门槛效应模型Threshold Effect分析数字金融指数与消费结构数据
前端·算法
随缘而动,随遇而安6 小时前
第八十八篇 大数据中的递归算法:从俄罗斯套娃到分布式计算的奇妙之旅
大数据·数据结构·算法
IT古董6 小时前
【第二章:机器学习与神经网络概述】03.类算法理论与实践-(3)决策树分类器
神经网络·算法·机器学习
水木兰亭9 小时前
数据结构之——树及树的存储
数据结构·c++·学习·算法
Jess0710 小时前
插入排序的简单介绍
数据结构·算法·排序算法
老一岁10 小时前
选择排序算法详解
数据结构·算法·排序算法
xindafu10 小时前
代码随想录算法训练营第四十二天|动态规划part9
算法·动态规划
xindafu10 小时前
代码随想录算法训练营第四十五天|动态规划part12
算法·动态规划