【算法1-6】二分查找与二分答案
学习:整数二分和实数二分
整数:在单调递增序列中查找 x 或者 x 的后继
def bin_search(a, n, x):
left = 0
right = n
while left < right:
mid = (left + right)//2
if a[mid] >= x:
right = mid
else:
left = mid + 1
return left
def bin_search2(a, n, x):
left, right = 0, n
while left < right:
mid = (left+right)//2
if a[mid] <= x:
left = mid
else:
right = mid - 1
return left
实数二分
eps = 1e-7 # 精度。根据题目设定
left, right = 0, 1 # 假设左右边界为0和1
while right - left > eps:
mid = (left+right)/2
if check(mid):
right = mid # 判定,然后继续二分
else:
left = mid
P2249 【深基13.例1】查找
bisect_right()
是 bisect
模块中的另一个函数,它与 bisect_left()
函数非常相似,但是有一个微妙的区别。
bisect_left()
函数返回的是待插入元素的位置,这个位置是从左边开始数的,即如果有相同的元素存在,那么插入的位置会是这些相同元素的左边。
而 bisect_right()
函数返回的也是待插入元素的位置,但是是从右边开始数的,即如果有相同的元素存在,那么插入的位置会是这些相同元素的右边。
import bisect
seq = [1, 2, 2, 3, 4]
# 使用 bisect_left 函数查找元素2的位置
left_index = bisect.bisect_left(seq, 2)
print(left_index) # 输出:1
# 使用 bisect_right 函数查找元素2的位置
right_index = bisect.bisect_right(seq, 2)
print(right_index) # 输出:3
import bisect
# 在有序序列中查找数字6应该插入的位置
index = bisect.bisect_left(seq, 6)
print(index) # 输出:3
# 将数字6插入到有序序列中的正确位置
bisect.insort_left(seq, 6)
print(seq) # 输出:[1, 3, 5, 6, 7, 9]
超时了,上面的
一直在想用二分后找到的不一定是第一个怎么办
结果可以用个while
while mid>0 and a[mid-1]==x:
mid-=1
return mid
def le_search(a, n, x):
left = 0
right = n - 1
while left <= right:
mid = (left + right) // 2
if a[mid] == x:
while mid>0 and a[mid-1]==x:
mid-=1
return mid+1###之前return和mid-=1对齐,错了
elif a[mid] < x:
left = mid + 1
else:
right = mid - 1
return -1
n, m = map(int, input().split())
a = list(map(int, input().split()))
lim = list(map(int, input().split()))
for i in lim:
print(le_search(a, n, i),end=" ")
还是超时了。。。
##为什么大佬的二分代码不用添加条件就可以查找第一个,而我的还要添加条件
def le_search(a, n, x):
left = 0
right = n - 1
count=-1
while left <= right:
mid = (left + right) // 2
if a[mid] == x:
count= mid+1
if a[mid] < x:
left = mid + 1
else:
right = mid - 1
return count
n, m = map(int, input().split())
a = list(map(int, input().split()))
lim = list(map(int, input().split()))
for i in lim:
print(le_search(a, n, i),end=" ")
P1102 A-B 数对
from itertools import *
N,C = map(int, input().split())
a = list(map(int, input().split()))
count=0
lis=list(permutations(a,2))
for i in range(len(lis)):
if lis[i][0]-lis[i][1]==C:
count+=1
print(count,end=" ")
超空间复杂度,改
from itertools import permutations
N, C = map(int, input().split())
a = list(map(int, input().split()))
count = 0
for x, y in permutations(a, 2):
if x - y == C:
count += 1
print(count, end=" ")
又超时间了。。。
N, C = map(int, input().split())
a = list(map(int, input().split()))
count = 0
# 创建一个字典,用于记录每个数值出现的次数
num_counts = {}
for num in a:
num_counts[num] = num_counts.get(num, 0) + 1
# 遍历列表,
#并检查每个数值加上或减去C后是否存在于列表中,并根据出现次数计算可能的组合数量。
for num in a:
# 计算num+C和num-C在列表中出现的次数
count += num_counts.get(num + C, 0)
count += num_counts.get(num - C, 0)
# 每对数值会被重复计算一次,因此需要除以2
print(count // 2, end=" ")
over,但有点没看懂
P1873 [COCI 2011/2012 #5] EKO / 砍树
通过一种更简单的方法来实现列表中每个元素减去列表中的第一个元素的值。
使用列表解析来实现这一目标。下面是一种简单的方法:
a = [x - a[0] for x in a]
N,M = map(int, input().split())
a = list(map(int, input().split()))
a.sort()
fheigh=a[0]
a = [x - a[0] for x in a]
def famu(a, m):
left, right = a[0], a[-1]
while left <= right:
cut = (right + left) // 2
num = 0
for i in range(len(a)):
num += max(0, a[i] - cut)
if num == m:
return cut
elif num > m:
right = cut - 1 # 将 right 缩小到 cut
else:
left = cut + 1
return right
print(a)
print(famu(a,M)+fheigh,end=" ")
##我的错误代码
def famu(a, m):
left, right = min(a), max(a)
while left <= right:
cut = (right + left) // 2
num = 0
for i in range(len(a)):
num += max(0, a[i] + fheigh - cut)
if num == m:
return cut
elif num > m:
left = cut + 1
else:
right = cut - 1
return right
# 示例用法(注意修改为您具体的参数)
N, M = map(int, input().split())
a = list(map(int, input().split()))
a.sort()
fheigh = a[0] # 最小高度
a = [x - fheigh for x in a] # 将所有树木的高度相对于最小高度调整
print(famu(a, M) , end=" ")
###正确代码
elif num > m:
left = cut + 1 # 将 right 缩小到 cut
else:
right = cut - 1
修改部分, 我不能理解,为什么num > m, left = cut + 1
在二分查找中,当我们发现当前切割高度 cut
对应的木材数量 num
大于目标木材需求 m
时,我们需要将搜索范围缩小。因为当前 cut
对应的木材数量过多,我们需要将切割高度调高一些以减少木材数量。高度cut越高,木材数量num越小
现在让我们详细解释一下为什么 left = cut + 1
是正确的:
- 当
num
大于m
时,表示当前的切割高度cut
太低了,所以我们需要将切割高度调高。 - 由于我们已经知道当前
cut
不满足条件,所以我们可以直接将搜索范围缩小到cut + 1
到right
之间。 - 这是因为,如果我们再次选择当前的
cut
进行切割,得到的木材数量依然会大于m
,我们不需要再次检查当前的cut
。 - 因此,我们将
left
更新为cut + 1
,继续在新的搜索范围内查找满足条件的切割高度。
这就是为什么当 num
大于 m
时,我们更新 left
为 cut + 1
。
N,M=map(int,input().split())
long=[]
long+=[int(x) for x in input().split()]
ans=max(long)
k=1
while(k<1000000):
sum=0
ans-=k
for i in range(N):
if long[i] >ans:
sum+=long[i]-ans
if sum>=M:
break
print(ans)
###暴力
P1024 [NOIP2001 提高组] 一元三次方程求解
暴力
a,b,c,d=map(int,input().split())
x=-100
tt=0.001
while x<100:
x+=0.01
if abs(a*(x**3)+b*(x**2)+c*(x)+d)<tt:
print("{:.2f}".format(x),end=" ")
这里使用 abs(a*(x**3)+b*(x**2)+c*(x)+d)<0.001
的判断条件是为了控制方程左边的值在很小的范围内,比如在0.001以内。这样做的目的是为了使得我们找到的解尽可能接近方程的精确解。
这个判断条件的含义是:如果方程左边的值的绝对值小于0.001,我们就认为方程的解是 x
。
在数值计算中,我们往往不能得到精确的解,特别是在处理浮点数时,存在舍入误差等问题。因此,我们希望找到一个足够接近方程的解,使得方程左边的值在可接受的误差范围内。这就是为什么我们使用 <0.001
这样一个很小的阈值来判断方程的解。abs()不能少
二分实数
a,b,c,d=map(int,input().split())
eps=0.01
def y(x):
return a*(x**3)+b*(x**2)+c*(x)+d
for i in range(-100,100):
left=i
right=i+1
if y(left)==0:
print("{:.2f}".format(left),end=" ")
if y(left)*y(right)<0:
while right-left>=eps:
mid=(right+left)//2
if y(mid)*y(right)<=0:
left=mid
else:
right=mid
print("{:.2f}".format(right),end=" ")
RE原因:改为a,b,c,d=map(eval,input().split())
a,b,c,d=map(eval,input().split())
a,b,c,d=map(int,input().split())
在这两个示例中,都是使用了 map()
函数来将输入的字符串转换为数字。然而,在第一个示例中使用了 eval()
函数,而在第二个示例中使用了 int()
函数。
在第一个示例中,map(eval, input().split())
将会将输入的字符串按空格分割后,使用 eval()
函数对每个分割后的字符串进行求值,因此它可以处理包含数字和运算符的字符串,例如 "1 + 2" 或 "3 * 4"。
而在第二个示例中,map(int, input().split())
将会将输入的字符串按空格分割后,使用 int()
函数将每个分割后的字符串转换为整数。这个方法适用于输入的字符串只包含整数,例如 "1 2 3 4"。
P1678 烦恼的高考志愿
m,n=map(int,input().split())
a=list(map(int,input().split()))
b=list(map(int,input().split()))
a.sort()
def xunzhao(i):
le=0
ri=len(a)-1
ans=10**7+1
while ri-le>=0:
mid=(le+ri)//2
if a[mid]>=i:
ri=mid-1
else:
le=mid+1
ans=min(abs(a[mid]-i),ans)
return ans
num=0
for i in b:
num+=xunzhao(i)
print(num)
##我开始在 if a[mid]>=i: 写的 ri=mid ,,,,,为什么有问题????
P2440 木材加工
for i in range(n):
lis.append([map(int,input().split())])
maxx+=int(input().split())
##报错
for i in range(n):
values = int(input())
lis.append(values)
maxx += (values)
几行的数字,以数字形式储存到列表
n, k = map(int, input().split())
maxx = 0
lis = []
for i in range(n):
values = int(input())
lis.append(values)
maxx += (values)
maxx=maxx//k
found = False
for l in range(maxx,0,-1):
total = 0
for num in lis:
total += num // l
if total == k:
print(l)
found = True
break
if not found:
print(-1)
###直接暴力,得了一半分
n, k = map(int, input().split())
lis = []
for i in range(n):
values = int(input())
lis.append(values)
left = 1 # 最小的长度为 1cm
right = max(lis) # 最大的长度为最长的原木长度
while left <= right:
mid = (left + right) // 2
total_pieces = sum(wood // mid for wood in lis) # 计算使用长度为 mid 时能够得到的小段木头的总数
if total_pieces >= k:
left = mid+1 # 如果能够得到的小段木头的总数大于等于 k,则尝试增加长度
else:
right = mid -1 # 否则,尝试减小长度
print(right) # 输出满足条件的最大长度
#AC
total_pieces = sum(wood // mid for wood in lis)
注意一下这种写法
这行代码使用了 Python 中的列表推导式和 sum()
函数来计算使用长度为 mid
时能够得到的小段木头的总数。
让我们详细解释一下这个用法:
-
for wood in lis
: 这部分是一个迭代,它会遍历列表lis
中的每个元素,即每根原木的长度。 -
wood // mid
: 对于每根原木的长度wood
,wood // mid
表示使用长度为mid
时能够从当前原木中切割出多少小段木头。这里使用地板除法//
是因为我们只考虑得到的小段木头的数量,而不考虑可能的余数。 -
sum(wood // mid for wood in lis)
: 这是一个列表推导式,它会遍历lis
中的每根原木,计算每根原木使用长度为mid
时能够得到的小段木头的数量,并将这些数量加起来。
这样,total_pieces
就表示使用长度为 mid
时能够得到的小段木头的总数。
“”“total_pieces+ =(wood // mid for wood in lis) ”“”这种方式是错的