蓝桥杯 -拔河

蓝桥杯 -拔河题解

题目

原题

问题描述

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

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

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

输入格式

输入共两行。

第一行为一个正整数 nn。

第二行为 nn 个正整数 aia**i。

输出格式

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

样例输入

text 复制代码
5
10 9 8 12 14

样例输出

text 复制代码
1

样例说明

其中一种最优选择方式:

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

评测用例规模与约定

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

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

思路

计算一个区间内的力量之和

计算一个区间内的力量之和需要使用前缀和。前缀和就是通过构造一个前缀和数组,构造方式为第 i i i个元素值为元素组中前 i i i个元素(包括第 i i i个元素)

这样的好处是计算区间 i , j i,j i,j值可以变成计算前缀和数组 a a a中 a j − a i − 1 aj-ai-1 aj−ai−1的值。这样时间复杂度就变成 O ( 1 ) O(1) O(1)了

暴力求解

暴力求解的思路大概是定义四个指针,分别表示左区间左右端点 l 1 , r 1 l1,r1 l1,r1和右区间左右端点 l 2 , r 2 l2,r2 l2,r2。同时需要满足以下条件 l 1 < = r 1 , r 1 < l 2 , l 2 < = r 1 l1<=r1,r1<l2,l2<=r1 l1<=r1,r1<l2,l2<=r1,同时为了避免重复计算,可以用两个指针先遍历出每个区间的值。基于此可以写出下列代码

python 复制代码
import bisect 
n = int(input())
a = list(map(int, input().split()))

# 计算前缀和
prefix_sum = [0] * (n + 1)
for i in range(1, n + 1):
    prefix_sum[i] = prefix_sum[i - 1] + a[i - 1]

value=[[0 for _ in range(n+1)] for _ in range(n+1)]
##先计算 降低时间复杂度
for i in range(1,n+1):
    for j in range(i,n+1):
        value[i][j]=prefix_sum[j]-prefix_sum[i-1]

ef=[]
# 降序排序。
for i in value:
    ef.append(sorted(i))
min_diff = float('inf')
# 遍历
for l1 in range(1,n):
    for r1 in range(l1,n):
        for l2 in range(r1+1,n+1):
            left=value[l1][r1]
            index=bisect.bisect_left(ef[l2],left)
            if index<len(ef[l2]):
                min_diff=min(abs(ef[l2][index]-left),min_diff)
            if index>1:
                min_diff=min(abs(ef[l2][index-1]-left),min_diff)
print(min_diff)

​ 在上述代码中如果使用4个指针那么时间复杂度就为 O ( n 4 ) O(n^4) O(n4),为了降低时间复杂度。这里先进行预处理。使用 e f ef ef存储计算出以i为该区间左端点的数据值。这里的 e f i j efij efij是指以 i i i为该区间左端点以j为右端点的区间的区间和 。实际使用的时候j的值不会影响结果。因此对每行排序从而方便二分查找。

接着遍历的时候时间复杂度就可以降低一个数量级为 O ( n 3 ) O(n^3) O(n3)。因为右区间的右端点不需要遍历。这是因为当确定了左区间值和右区间左端点的时候。只需要找到在使用该左端点的右区间中和左区间值最接近的值。而我们不关心这个右端点的值。在求出df数组下就可以直接使用二分查找这个值。

​ 但是使用该思路时间复杂度还是达到 O ( n 3 ) O(n^3) O(n3),此外还有预处理数组ef需要 O ( n 2 ) O(n^2) O(n2)以及计算前缀和需要 O ( n ) O(n) O(n)可以忽略。时间复杂度过高。使用该代码只能通过 75 75 75% 的测试用例。

正解思路

正解代码极其简单。思路是先计算前缀和,然后通过前缀和得到所有区间的值。找到这些值里面相差最小的值。时间复杂度为 O ( n 2 ) O(n^2) O(n2)代码如下:

python 复制代码
input()
data=list(map(int,input().split()))
predata=[data[0]]
for i in data:
    predata.append(predata[-1]+i)

value=[]
# 遍历所有区间的值
for i in range(1,len(predata)):
    for j in range(i,len(predata)):
        value.append(predata[j]-predata[i-1])
value=sorted(value)
ans=1e11
# 无所谓重复,因为重复相当于不去中间重复部分
for i in range(len(value)-1):
    ans=min(abs(value[i]-value[i+1]),ans)
print(ans)

可能你会有疑问:为什么这样可以通过,不考虑重复吗。

实际上,重复不影响结果。因为如果选中的两个区间重复了那么必定还有两个区间是这两个区间由该重复区间非重复段组成。具体来说就是,例如下图中两个重复区间:

那么取这两个区间中的非重复段得到的结果必定和两个非重复部分结果相同。换句话说就是可以去上述区间,那么也可以取下图这两个区间。那么这两个区间结果值和上面这两个重复区间的结果是相同的

因此可以不用考虑重复段。

相关推荐
lzhdim2 小时前
AI的分类
人工智能·算法·机器学习·分类·数据挖掘
凤凰院凶涛QAQ4 小时前
《Java版数据结构 & 集合类剖析》Map & Set & 反射:“从哈希碰撞到红黑树转换,反射帮你扒开 HashMap 的桶数组看个究竟”
java·数据结构·算法
w_zero_one6 小时前
算法--模拟
java·数据结构·算法
秋天的怀念_David7 小时前
算法一千零一夜 · 第十三夜 幻方,武侠世界里的数学迷宫
java·程序人生·算法
秋天的怀念_David7 小时前
算法一千零一夜 · 第十四夜 幻方与奇妙的二进制
java·程序人生·算法
薛定e的猫咪7 小时前
(ICLR 2020)MAML 靠的不是快速学习,而是特征复用
人工智能·深度学习·学习·算法·机器学习
薛定e的猫咪8 小时前
(ICLR 2026)DF-META:面向 FJSP 的无导数元学习实例自适应调度
人工智能·深度学习·学习·算法·机器学习
小羊没烦恼!9 小时前
异步编程:线程概述及使用
java·开发语言·mysql·算法·c#
wanderist.10 小时前
202609CSP认证P3
算法
云淡风轻~窗明几净10 小时前
角谷猜想最接近严格证明的测试 Version1.1.1
人工智能·算法·机器学习·图论