2024年第十五届蓝桥杯省赛大学生C++B组题解&个人感受

前言

作者水平一般~~,写博客是为了记录自己的刷题过程来帮助自己以后复习

1、整体难度不大,编程题打暴力几乎都可以拿一些分,除了基础算法偏多,锻炼思维能力

2、一定要记住 开long long啊~~ 大部分题不开都会爆int的,忘了两次(┭┮﹏┭┮

3、该暴力的题一定要暴力,C题和G题都是直接的暴力题,没有那么多限制~~

4.   multiset<int> 这个可重复集合的数据结构该用到的时候一定不要忘记qwq

题目A : 握手问题

问题描述

小蓝组织了一场算法交流会议,总共有 50 人参加了本次会议。在会议上,大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手 (且仅有一次)。但有 77 个人,这 7 人彼此之间没有进行握手 (但这 7 人与除这 7 人以外的所有人进行了握手)。请问这些人之间一共进行了多少次握手?

注意 A 和 B 握手的同时也意味着 B 和 A 握手了,所以算作是一次握手。

答案提交

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

解析

比较签到的填空题, 分成两堆人来思考

 43个人每人握手42个人(彼此握手需要/2),有7个人需要握手43个人 

代码

#include <iostream>
using namespace std;
int main()
{
  cout <<  43 * 42 / 2 + 43 * 7  <<endl;
  
return 0;
}

 

题目B:小球反弹

问题描述

有一长方形,长为 343720 单位长度,宽为 233333 单位长度。在其内部左上角顶点有一小球 (无视其体积),其初速度如图所示且保持运动速率不变,分解到长宽两个方向上的速率之比为 dx:dy=15:17。小球碰到长方形的边框时会发生反弹,每次反弹的入射角与反射角相等,因此小球会改变方向且保持速率不变(如果小球刚好射向角落,则按入射方向原路返回)。从小球出发到其第一次回到左上角顶点这段时间里,小球运动的路程为多少单位长度?答案四舍五入保留两位小数。

答案提交

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个小数,在提交答案时只填写这个小数,填写多余的内容将无法得分。

解析

刚写的时候看出来这题的目的就是让我们去找角,找到一个角必然会原路返回到出发点,但是如何找到角呢......

本质上每次反弹就相当于换了一次镜像的方向继续去寻找角,那我们更直观一点是不是可以直接相当于在一个新的长方形区域中继续走呢,最后的效果是一模一样的

因此在我们新的思路下,当我们碰到了墙壁t次 则纵轴方向的距离我们是可以直接知道的,就是233333 * t ,那我们横轴方向上走的距离是不是也知道呢,只要我们横轴方向上走的距离是长方形区域的横轴边的整数倍,那是不是就意味着我们找到了任意的一个角呢~~~

下面是一个比较好理解的图~

那么可能有同学要问了,我们有没有可能找到的第一个角就是起点的角呢?

这是不可能的,至于上面的图是为了帮助大家理解过程,实际上是有一点问题的/(ㄒoㄒ)/~~。至于为什么不可能大家可以去思考,存不存在一种情况从左上角出发而不经过其它任何一个角,就能反弹回到左上角呢?(这里偷个懒)

问题:是否存在从长方形左上角出发、不经过其他任何角,仅通过反弹最终回到左上角的路径?

答案不可能

原因

  1. 反弹规则:路径在长方形边界的反弹遵循“入射角=反射角”的物理规律,每次碰到边会改变方向。

  2. 必须经过其他角

    • 要从起点反弹回起点,路径必须完全反向(即同时反转水平和垂直方向)。

    • 只有碰到另一个角(如右上、左下或右下角)时,才能同时反转两个方向,直接折返起点。

    • 如果仅碰到非角的边,只能反转一个方向(水平或垂直),无法直接返回原点。

  3. 数学本质

    • 路径回到原点需满足位移累积为零,这要求路径必须经过至少一个其他角来实现方向的全反转。

代码

#include<bits/stdc++.h>
#define int long long

using namespace std;

signed main(){
	// 假设 反弹了 t 次
	// 在横轴 上走的距离是  ( 233333 * t * 15 / 17 )  % 343720 == 0  
	// 故(  233333 * t * 15 % ( 17 * 343720) == 0 ) 可以说明走到角落 
	int t = 1 ;
	int y = 17 * 343720;
	int ll;
	while(1){
		ll = 233333 * t * 15;
		if( ll % y == 0){
			break;
		}
		t++;
	}
	double c = sqrt(15 * 15 + 17 * 17); // 斜边长度
	double b = 17; // 竖着的直角边长度
	double ans =233333 * t  * b / c;
	printf("%.2lf" , ans * 2);
	
	
	return 0;
}

题目C :好数

问题描述

一个整数如果按从低位到高位的顺序,奇数位 (个位、百位、万位 ⋯⋯ ) 上的数字是奇数,偶数位 (十位、千位、十万位 ⋯⋯ ) 上的数字是偶数,我们就称之为 “好数”。

给定一个正整数 N,请计算从 1 到 N 一共有多少个好数。

输入格式

一个整数 N。

输出格式

一个整数代表答案。

样例输入 1

24

样例输出 1

7

样例输入 2

2024

样例输出 2

150

样例说明

对于第一个样例,24 以内的好数有 1、3、5、7、9、21、23,一共 7 个。

评测用例规模与约定

对于 10%的评测用例,1≤N≤100 。

对于 100% 的评测用例,1≤N≤10^7 。

解析

在第一眼看到这题时,以为是找规律什么的思维题,后来发现就是一个暴力题啊~~

因为题目上给出的n最大的范围是1e7,就说明了n最多是有七位数字,所以对于一个数字最多检查七次,所以对于任何测试样例,计算的总次数都不会超过 1e7 * 7 这个范围 , 而对于C++代码中的操作次数控制在 1e7 ~ 1e8 为最佳,因此不用担心超时的问题。

 

代码

#include<bits/stdc++.h>

using namespace std;

bool check_j(int x){ //检查奇数位置 
	while(x){
		int d = x % 10;
		if( d % 2 != 1) return false;
		x /= 100;
	}
	return true;
}

bool check_o( int x){ // 检查偶数位置 
	while(x){
		x /= 10;
		int d = x % 10;
		if( d % 2 != 0) return false;
		x/=10;
	}
	return true;
}


bool check( int x){
	if(check_j(x) && check_o(x)) return true;
	return false;
}



int main(){
	int n;
	cin >> n ;
	int sum = 0 ;
	for(int i = 1 ;  i<= n ;  i++){
		if(check(i)) sum ++ ;
	}
	
	cout << sum <<endl;

	return 0;
}

 

题目D :R格式

问题描述

小蓝最近在研究一种浮点数的表示方法:R 格式。对于一个大于 0 的浮点数 d,可以用 RR 格式的整数来表示。给定一个转换参数 nn,将浮点数转换为 R 格式整数的做法是:

  1. 将浮点数乘以 2^n;

  2. 四舍五入到最接近的整数。

输入格式

一行输入一个整数 n 和一个浮点数 d,分别表示转换参数,和待转换的浮点数。

输出格式

输出一行表示答案:d 用 R 格式表示出来的值。

样例输入

2 3.14

样例输出

13

 

样例说明

3.14×22=12.56四舍五入后为 13。

评测用例规模与约定

对于 50%的评测用例:1≤n≤10,1≤ 将 d 视为字符串时的长度 ≤15。

对于 100%的评测用例:1≤n≤1000,1≤ 将 d 视为字符串时的长度 ≤1024;保证 d 是小数,即包含小数点。

解析

本来以为是快速幂 ,认真一看原来是高精度~~

就是给一个浮点数让你给他乘上 n 个 2,根据题目的限制,最多*2 1000次,而输入的数字最多是一个15位数,最后结果也最多1e15*(2^1000) <=1e15*(10^1000) = 1e1015位,最后存放结果的数组最多就是1015位,计算1000次,运算次数不会超过1e7,在高精度运算的范围内

思路是这样的,但是由于长时间不写高精度导致代码有一些冗杂,仅供参考()

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long

vector<int>A; 
vector<int> add(vector<int>ans){ //+ 1
	vector<int>C;
	int t= 0;
	for(int i = ans.size() - 1; i >= 0 ; i --){
		int d = ans[i];
		if( i == ans.size() - 1) d++;
		C.push_back( (d + t) % 10 );
		t = (d + t) / 10;
	}
	while(t){
		C.push_back(t % 10);
		t /= 10;
	}
	ans.clear();                            //更新结果
	for(int i =  C.size() -1;  i >= 0; i --) {
		ans.push_back(C[i]);
	}
	return ans;
}

void mul(){ // * 2
	vector<int>C;
	int t = 0;
	// 6280
	for(int i = A.size() - 1;  i >= 0 ; i --){
		int d = A[i];
		C.push_back( (d * 2 + t) % 10 ) ;
		t = (d * 2 + t) / 10;
	}
	while(t){
		 C.push_back(t % 10);
		 t /= 10;
	}
	A.clear();                                   //更新结果
	for(int i =  C.size() -1;  i >= 0; i --) A.push_back(C[i]);
}

signed main()
{
  int n;
  string f_string;
  cin >> n >> f_string;
  int t= f_string.find('.');
 
  t = f_string.size() - t - 1; // 找到小数点后面第一位在数组中的位置

  for(int i = 0; i <= f_string.size() -1 ; i++ ){
  	if(f_string[i] != '.')A.push_back(f_string[i] - '0');
  }

	for(int i = 0 ; i < n ; i ++) mul();

	int flag = 0; //判断需不需要进1

	vector<int>ans(A.begin(), A.end() - t);  //最后的结果要把小数位去掉,如果需要+1再+1即可

	if(A[A.size() - t] >= 5){

		 ans = add(ans);
	}
	string s ="";

	for(int i = 0 ; i<= ans.size() - 1 ; i ++) cout << ans[i];

  return 0;
}

 

题目E :宝石组合

问题描述

在一个神秘的森林里,住着一个小精灵名叫小蓝。有一天,他偶然发现了一个隐藏在树洞里的宝藏,里面装满了闪烁着美丽光芒的宝石。这些宝石都有着不同的颜色和形状,但最引人注目的是它们各自独特的 “闪亮度” 属性。每颗宝石都有一个与生俱来的特殊能力,可以发出不同强度的闪光。小蓝共找到了 N 枚宝石,第 i 枚宝石的 “闪亮度” 属性值为 Hi​,小蓝将会从这 NN 枚宝石中选出三枚进行组合,组合之后的精美程度 S 可以用以下公式来衡量:

小蓝想要使得三枚宝石组合后的精美程度 S 尽可能的高,请你帮他找出精美程度最高的方案。如果存在多个方案 S 值相同,优先选择按照 H 值升序排列后字典序最小的方案。

输入格式

第一行包含一个整数 N 表示宝石个数。

第二行包含 N 个整数表示 N 个宝石的 “闪亮度”。

输出格式

输出一行包含三个整数表示满足条件的三枚宝石的 “闪亮度”。

样例输入

5
1 2 3 4 9

 

样例输出

1 2 3

评测用例规模与约定

对于 30%的评测用例:3≤N≤100,1≤Hi≤1000 。

对于 60% 的评测用例:3≤N≤2000 。

对于 100% 的评测用例:3≤N≤100000,1≤Hi≤100000 。

解析

其实题目看起来很复杂,我们可以打表发现任意三个数的精美度就是这三个数的最大公约数

至于证明过程可以见y总:

第十五届蓝桥杯C++ B组编程题_哔哩哔哩_bilibili

我们已经知道了要找到三个数的最大公约数最大的前提下字典序最小,我们如果直接枚举数字显然是会超时的,但是我们可以换个思维,我们直接去枚举精美度,时间复杂度分析如下:

    for(int i = 1e5 ;  i >= 1 ; i --)
        for(int j = i  ; j < N ; j += i)

外层循环最多是 N次,即1e5 次,而内层循环是一个调和级数的形式,根据调和级数的性质是ln(N)的时间复杂度,所以总的时间复杂度就是 N*log(N) ≈ 10^5×11.5=1.15×10^6,远远不会超时

这里我们精美度是从大到小枚举的,而数字是从小到大枚举的,就完美的实现了找到的第一组数一定是我们要找的答案 。

代码 

#include<bits/stdc++.h>
using namespace std;
// 核心思路是 倒序枚举精美度 找到符合条件的3个宝石
// num[3] 来记录数字 倒叙寻找只要找到符合数字就输出 
const int N = 100010;
int s[N];
int nums[3];  
int main()
{
    int n , x;
    cin >> n ;
    for(int i = 0 ; i < n ; i ++) {
        cin >> x;
        s[x] ++ ;
    }
    int flag = 0;  					   //  用来标记是否完成 
    for(int i = 1e5 ;  i >= 1 ; i --){ // 枚举精美度  从大到小  美剧数字从小到大 可以保证找到的符号题目要求 
        int cnt = 0;
        for(int j = i  ; j < N ; j += i){ 
            if(s[j]) for(int k = 0 ; k < s[j] ; k++) {
                nums[cnt++] = j;
                if(cnt == 3)break;
            }
            if(cnt == 3){
                flag = 1;
                for(int ii = 0 ; ii < 3 ; ii ++){
                    if(ii != 0 )cout << " ";
                    cout << nums[ii];
                }
                break;
            }
        } 
        if(flag) break;
    }

  return 0;
}

 

题目F :数字接龙

问题描述

小蓝最近迷上了一款名为《数字接龙》的迷宫游戏,游戏在一个大小为 N×N 的格子棋盘上展开,其中每一个格子处都有着一个 0…K−1之间的整数。游戏规则如下:

  1. 从左上角 (0,0) 处出发,目标是到达右下角 (N−1,N−1) 处的格子,每一步可以选择沿着水平/垂直/对角线方向移动到下一个格子。

  2. 对于路径经过的棋盘格子,按照经过的格子顺序,上面的数字组成的序列要满足:0,1,2,…,K−1,0,1,2,…,K−1,0,1,2… 。

  3. 途中需要对棋盘上的每个格子恰好都经过一次(仅一次)。

  4. 路径中不可以出现交叉的线路。例如之前有从 (0,0) 移动到 (1,1) ,那么再从 (1,0)移动到 (0,1) 线路就会交叉。

为了方便表示,我们对可以行进的所有八个方向进行了数字编号,如下图 2 所示;因此行进路径可以用一个包含 0…7 之间的数字字符串表示,如下图 1 是一个迷宫示例,它所对应的答案就是:41255214。

现在请你帮小蓝规划出一条行进路径并将其输出。如果有多条路径,输出字典序最小的那一个;如果不存在任何一条路径,则输出 −1。

输入格式

第一行包含两个整数 N,K。

接下来输入 N 行,每行 N 个整数表示棋盘格子上的数字。

输出格式

输出一行表示答案。如果存在答案输出路径,否则输出 −1。

解析

看题目很容易可以看出来是dfs,但是容易忽略特殊情况

需要注意的是对于斜着走的四个方向不仅要判断能不能走过去,还要判断和之前的路径有没有交叉

直观的想就是四个方向判断四次对吧,错误代码如下(惨痛教训/(ㄒoㄒ)/~~)不要学我~

代码(过75%)

bool check(int x, int y, int dx, int dy) {
    if (dx == 1 && dy == 1) { // 右下方向
        return !(visited[x][y+1] && visited[x+1][y]);
    }
    if (dx == -1 && dy == -1) { // 左上方向
        return !(visited[x][y-1] && visited[x-1][y]);
    }
    if (dx == 1 && dy == -1) { // 左下方向
        return !(visited[x][y-1] && visited[x+1][y]);
    }
    if (dx == -1 && dy == 1) { // 右上方向
        return !(visited[x-1][y] && visited[x][y+1]);
    }
    return true;
}

但是如果这样写的话只能通过75%的样例,对于稍微极端一点的情况是不适用的,如下:

这个题唯一难一点的就是对于有点极限的特殊情况

上图中蓝色的0点需要往坐下走是最优的,但是会因为它左侧的2和下侧的2都已经经过,所以就不允许它通过,错失了走到最优路径的方法,因此上述方法是不行滴~

而直接用edges四维数组来标记就很合理啦 edge[x][y][tx][ty]就代表 (x , y)和(tx, ty)之间有一条边

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 11;
int a[N][N], visited[N][N], edges[N][N][N][N];
int n, k;
vector<string> ans;
string path;

// 方向数组: 上, 下, 右, 左, 右下, 左下, 右上, 左上
const int dx[] = {-1, 1, 0, 0, 1, 1, -1, -1};
const int dy[] = {0, 0, 1, -1, 1, -1, 1, -1};
const char dir_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7'};

bool check_move(int i, int j, int dir_x, int dir_y) {
    if (dir_x == 1 && dir_y == 1)   // 右下方向
        return !edges[i][j+1][i+1][j];
    if (dir_x == -1 && dir_y == -1) // 左上方向
        return !edges[i][j-1][i-1][j];
    if (dir_x == 1 && dir_y == -1)  // 左下方向
        return !edges[i][j-1][i+1][j];
    if (dir_x == -1 && dir_y == 1)  // 右上方向
        return !edges[i-1][j][i][j+1];
    return true;
}

char get_dir_char(int dx, int dy) {
    for (int i = 0; i < 8; i++)
        if (dx == ::dx[i] && dy == ::dy[i])
            return dir_chars[i];
    return ' ';
}

void dfs(int x, int y, int last) {
    if (x == n && y == n && path.size() == n * n - 1) {
        ans.push_back(path);
        return;
    }

    for (int i = 0; i < 8; i++) {
        int tx = x + dx[i], ty = y + dy[i];
        if (tx < 1 || tx > n || ty < 1 || ty > n || visited[tx][ty] || 
            a[tx][ty] != (last + 1) % k || !check_move(x, y, dx[i], dy[i]))
            continue;

        visited[tx][ty] = 1;
        path += dir_chars[i];
        edges[x][y][tx][ty] = edges[tx][ty][x][y] = 1;

        dfs(tx, ty, a[tx][ty]);

        edges[x][y][tx][ty] = edges[tx][ty][x][y] = 0;
        path.pop_back();
        visited[tx][ty] = 0;
    }
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];

    visited[1][1] = 1;
    dfs(1, 1, a[1][1]);

    if (ans.empty()) {
        cout << -1;
    } else {
        sort(ans.begin(), ans.end());
        cout << ans[0];
    }

    return 0;
}

 

题目G :爬山

问题描述

小明这天在参加公司团建,团建项目是爬山。在 x 轴上从左到右一共有 n座山,第 i 座山的高度为 hi。他们需要从左到右依次爬过所有的山,需要花费的体力值为 S = Σni=1hi。

然而小明偷偷学了魔法,可以降低一些山的高度。他掌握两种魔法,第一种魔法可以将高度为 H 的山的高度变为 ⌊√H⌋,可以使用 P 次;第二种魔法可以将高度为 H 的山的高度变为 ⌊H/2⌋,可以使用 Q 次。并且对于每座山可以按任意顺序多次释放这两种魔法。

小明想合理规划在哪些山使用魔法,使得爬山花费的体力值最少。请问最优情况下需要花费的体力值是多少?

输入格式

输入共两行。

第一行为三个整数 n,P,Q。

第二行为 n 个整数 h1,h2,. . . ,hn。

输出格式

输出共一行,一个整数代表答案。

样例输入

4 1 1
4 5 6 49

样例输出

18

提示

【样例说明】将第四座山变为 ⌊√49⌋ = 7,然后再将第四座山变为 ⌊7/2⌋ = 3。体力值为 4 + 5 + 6 + 3 = 18。

【评测用例规模与约定】

对于 20% 的评测用例,保证 n ≤ 8,P = 0。

对于 100% 的评测用例,保证 n ≤ 100000,0 ≤ P ≤ n,0 ≤ Q ≤ n,0 ≤ hi ≤ 100000。

解析

很贪心的思想就是对于大的数字直接开根,对于小的数字再进行/2,这样确实可以过掉大部分用例,用最大堆来存数代码也很简洁过掉90%,这题还是很友好的

代码(过90%)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100010;
int x , ans; 

signed main(){
	priority_queue<int>q;
	int n , P , Q;
	cin >> n >> P >> Q;
	for(int i = 0 ; i < n ; i ++){
		cin >> x;
		q.push(x);
	}
	while( P > 0 && q.size()){
		int t = q.top();
		t = sqrt(t);
		q.pop();
		if(t) q.push(t);
		P --;
	}
	while( Q > 0 && q.size()){
		int t = q.top();
		t = t / 2;
		q.pop();
		if(t) q.push(t);
		Q--;
	}
	while(q.size()){
		auto t = q.top();
		ans += t;
		q.pop();
	}
	cout << ans <<endl;
	return 0;
} 

但是还有一些极端的情况会被hack,这是为什么呢,见如下反例

反例:

比如两座山分别是 48 和 49 , 我们可以两个操作分别一次

我们按照贪心的思想得到的结果就是 sqrt(49) + (48/2) = 31

而正解应该是  sqrt(48) + (49/2) = 30

即盲目开根不能解决所有问题的时候需要先对大的数/2

我们去思考这个问题就会发现,当一座山的高度为 hi

1、hi是一个完全平方数且是一个奇数

2、存在hi-1高度的山

3、还要注意 高度为hi的山的数量 + 高度为hi-1的数量 > P

(P为能开根的次数,如果我开根次数足够多那你除2不仅多此一举还可能让我直接错过最优解,必须在完美的时间点去除2)

满足上面三个必要条件才能对最大的数字进行先除后根处理,否则都需要先根后除

——优化是我们加一个check()判断每个时间存不存在上面的特殊情况即可

AC代码中的三个while可以合并成一个while(P||Q)写,但是为了清晰一点就直接放上去了~

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100010;
int x , ans; 
int n , P , Q;
int h[N]; //用来存每个高度的山的数量 
bool check(int x){ //判断是不是我们说的那一种特殊情况 
	int sq = sqrt(x);
	if( x > 0 && sq * sq == x && h[x-1] > 0 && sq % 2 == 1 && h[x] + h[x-1] > P) return true;
	else return false;
}

signed main(){
	priority_queue<int>q;
	cin >> n >> P >> Q;
	for(int i = 0 ; i < n ; i ++){
		cin >> x;
		h[x] ++ ;  
		q.push(x);
	}
	while(P > 0 && Q > 0 && q.size()){
		int t = q.top();
		q.pop();
		if(check(t)){
			h[t]--;
			t /= 2;
			if(t){
				q.push(t);
				h[t]++;
			}
			Q--;
		}else{
			h[t]--;
			t = sqrt(t);
			if(t) {
				q.push(t);
				h[t]++;
			}
			P --;
		}
	}
	
	while( P > 0 && q.size()){
		int t = q.top();
		h[t]--;
		t = sqrt(t);
		q.pop();
		if(t) {
			q.push(t);
			h[t]++;
		}
		P --;
	}
	while( Q > 0 && q.size()){
		int t = q.top();
		h[t]--;
		t = t / 2;
		q.pop();
		if(t) {
			q.push(t);
			h[t]++;
		}
		Q--;
	}
	while(q.size()){
		int t = q.top();
		ans += t;
		q.pop();
	}
	cout << ans <<endl;
	return 0;
} 

题目H :拔河

问题描述

小明是学校里的一名老师,他带的班级共有 nn 名同学,第 ii 名同学力量值为 aiai​。在闲暇之余,小明决定在班级里组织一场拔河比赛。

为了保证比赛的双方实力尽可能相近,需要在这 nn 名同学中挑选出两个队伍,队伍内的同学编号连续:{al1,al1+1,…,ar1−1,ar1}和 {al2,al2+1,…,ar2−1,ar2},其中l1​≤r1​<l2​≤r2​。

两个队伍的人数不必相同,但是需要让队伍内的同学们的力量值之和尽可能相近。请计算出力量值之和差距最小的挑选队伍的方式。

输入格式

输入共两行。

第一行为一个正整数 n。

第二行为 n 个正整数 ai​。

输出格式

输出共一行,一个非负整数,表示两个队伍力量值之和的最小差距。

样例输入

5
10 9 8 12 14

样例输出

1

样例说明

其中一种最优选择方式:

队伍 1: {a1,a2,a3},队伍 2:{a4,a5},力量值和分别为 10+9+8=27 , 12+14=26,差距为 ∣27−26∣=1。

评测用例规模与约定

对于 20% 的评测用例,保证n≤50 。

对于 100% 的评测用例,保证 n≤103,ai≤109 。

解析

这题是唯一的一题可能一分拿不到的~~,方向找错了(/(ㄒoㄒ)/~~

题目上说的是 l1≤r1<l2≤r2,意思就是只要在总的(1 , n)区间中任意两个不相交的区间就是合法的区间啊,我一直以为只有找出一个相邻的两个区间才是合法的(理了个大埔)

正解思路:前缀和+二分

就是把所有的区间和都枚举出来,存入到一个multiset<int>形式的st集合中去,我们通过枚举左区间和,来二分查找右区间和中最接近左区间的,进行更新ans

	for(int i = 1; i < n ; i++)
		for(int j = i ; j <= n ; j ++){ // 只要包含右端点i就需要删除 
			int k = s[j] - s[i-1];
			st.erase(st.find(k));
		}

而我们如何才能快速找到右区间和中最接近左区间的内容呢?

这就要用到了集合的删除功能,我们从i = 1开始枚举左区间的右端点,我们只要把包含i的区间都给删去,这样就不会出现选的右区间中包含i这个位置,因为我们i是从1到n遍历的,自然也不会出现i左侧的区间都已经不在st中。

(先把包含i =1的右区间删除,查找一次并更新一次ans,把i=2的右区间删除,查找一次更新一次ans......),可以保证每次查找不合法的右区间都已经st.erase()删去了

——这样我们在集合中找到的区间和就一定是右区间

代码

#include<bits/stdc++.h>

using namespace std;
#define int long long

const int N = 1010;
int a[N] , s[N];
int ans = INT_MAX;
multiset<int>st;

signed main(){
	int n;
	cin >> n ;
	for(int i = 1 ; i <= n ;  i ++ ) cin >> a[i];
	for(int i = 1 ; i <= n ; i ++) s[i] = s[i-1] + a[i];
	
	for(int i = 1 ; i <= n ; i ++){ // 枚举 全部区间 
		 for(int j = i ; j <= n ; j ++){
		 	st.insert(s[j] - s[i-1]); // 全部假如 
		 }
	}
	for(int i = 1; i < n ; i++){
		for(int j = i ; j <= n ; j ++){ // 只要包含右端点i就需要删除 
			int k = s[j] - s[i-1];
			st.erase(st.find(k));
		}
		for(int j = 1 ; j <= i ; j ++){
			int p = s[i] - s[j - 1];
			auto t = st.lower_bound(p);
			if( t != st.end()) ans = min( ans , *t- p );
			if( t != st.begin()){
				t--;
				ans = min(ans , p - *t);
			} 
		}
	}
	cout << ans << endl;
	 
	return 0;
}

就写到这儿啦,希望大家蓝桥杯都能稳稳省一以上~~(2025.4.2)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值