玩玩luogu算法题——第1期

昨天已经把所有大作业写完了,所以今天就想去做一些有趣的事情...
今天做的题都不是特别难,除了最后一题(写了大概1000多行Markdown,结果能Accepted的代码居然只有十几行?!)

目标:希望暑假的时候每天都能更新一点算法题的随笔出来,加油~

P1000 超级玛丽游戏(一个非常入门的题目,作用是用来快速恢复状态)

Star Rating: 1.05

题目描述

超级玛丽是一个非常经典的游戏。请你用字符画的形式输出超级玛丽中的一个场景。

********
************
####....#.
#..###.....##....
###.......###### ### ###
........... #...# #...#
##*####### #.#.# #.#.#
####*******###### #.#.# #.#.#
...#***.****.*###.... #...# #...#
....**********##..... ### ###
....**** *****....
#### ####
###### ######
##############################################################
#...#......#.##...#......#.##...#......#.##------------------#
###########################################------------------#
#..#....#....##..#....#....##..#....#....#####################
########################################## #----------#
#.....#......##.....#......##.....#......# #----------#
########################################## #----------#
#.#..#....#..##.#..#....#..##.#..#....#..# #----------#
########################################## ############

输入格式

输出格式

如描述

Solution:

如果是C或者C++的话,需要逐行进行输出(printf/cout),如果是Python的话就非常简单,直接用三个单引号的字符串语法搞定

print(''' ********
************
####....#.
#..###.....##....
###.......###### ### ###
........... #...# #...#
##*####### #.#.# #.#.#
####*******###### #.#.# #.#.#
...#***.****.*###.... #...# #...#
....**********##..... ### ###
....**** *****....
#### ####
###### ######
##############################################################
#...#......#.##...#......#.##...#......#.##------------------#
###########################################------------------#
#..#....#....##..#....#....##..#....#....#####################
########################################## #----------#
#.....#......##.....#......##.....#......# #----------#
########################################## #----------#
#.#..#....#..##.#..#....#..##.#..#....#..# #----------#
########################################## ############''')

Accepted, 6pp(New!)
15ms, 3.72MB
Overall pp: 6pp(+6pp)

P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题

Star Rating: 2.16

题目描述

输入两个正整数 x0,y0,求出满足下列条件的 P,Q 的个数:

  1. P,Q 是正整数。

  2. 要求 P,Qx0 为最大公约数,以 y0 为最小公倍数。

试求:满足条件的所有可能的 P,Q 的个数。

输入格式

一行两个正整数 x0,y0。(输入只有两个数)

输出格式

一行一个数,表示求出满足条件的 P,Q 的个数。(输出只有一个数)

样例 #1

样例输入 #1

3 60

样例输出 #1

4

提示

P,Q4 种:

  1. 3,60
  2. 15,12
  3. 12,15
  4. 60,3

对于 100% 的数据,2x0,y0105

【题目来源】

NOIP 2001 普及组第二题

Try to Solve--

首先我们能想到的一种方法是把所有的数列举出来,这是可行的一种最慢的方法了;不过我们肯定不会用这种方法,寻找它们一定有某种技巧可循。

我们知道lcm(a,b)max(a,b),所以在求解范围中的ab一定都小于lcm(a,b),也就是输入的y0,这样也就缩小了寻找的范围。

另外我们知道ab都可以整除gcd(a,b),这样也就把范围进一步缩小到小于y0gcd(a,b)的倍数上。

在上面的例子上,我们可以发现,解是对称的,也就是说我们只要找到其中一半解,然后乘以2就可以(如果这里面两个数不相等就可以这样做,如果它们相等,我们之后会单独讨论),所以这样就让搜索范围缩小到b.

另外,gcd(a,b)lcm(a,b)a,b之间有一个很重要的性质,就是a×b=gcd(a,b)×lcm(a,b),可以利用这个性质进一步缩小搜索范围。

a=b时,会发生什么呢?此时一定有gcd(a,b)=lcm(a,b),因此这种情况只有在两个输入相等的时候才会发生,所以此时直接输出1即可。

这样我们就可以写出第一个版本的代码:

import math
# 输入两个数字a,b
a,b = map(int(input().split()))
if a == b:
print(1)
else:
# 在[0,b]范围内,按照步长a进行搜索
count = 0
for i in range(a,math.sqrt(b),a):
if b % i == 0:
i += 1
print(count)

这就是我们第一个版本的代码,它的时间复杂度已经达到了O(b)

我们把代码提交以后,发现所有样例全都是runtime error, 为什么呢?

Your code has a few issues that could result in a runtime error. Here are some suggestions to fix the code:

  1. Input Parsing:

    • The line a,b = map(int(input().split())) will raise a ValueError because map expects a function as its first argument. You should use list(map(int, input().split())) or simply a, b = map(int, input().split()).
  2. Loop Range:

    • The loop for i in range(a, math.sqrt(b), a) will not work as expected. The range function expects integer arguments, but math.sqrt(b) may result in a float. You should convert math.sqrt(b) to an integer, e.g., int(math.sqrt(b)).
  3. Counting Logic:

    • The count logic inside the loop is missing. You should increment the count variable when you find a valid pair (P, Q). Also, you should use range(a, int(math.sqrt(b)) + 1, a) to include the upper bound.

Here is an updated version of your code:

import math
# Input two numbers, a and b
a, b = map(int, input().split())
if a == b:
print(1)
else:
# In the range [0, sqrt(b)], check for valid pairs
count = 0
for i in range(a, int(math.sqrt(b)) + 1, a):
if b % i == 0:
count += 1
# Additional logic to handle the symmetric case
if i != b // i:
count += 1
print(count)

This should address the mentioned issues and provide the correct output for the given problem.

ChatGPT给优化了一下,至少能运行了,现在的分数是40.

Current pp: 3pp(+3pp)

Overall pp: 9pp(+3pp)

检查下ChatGPT的代码和我们之前的逻辑,稍微进行了一下优化:

import math
# 输入两个数字a和b
a, b = map(int, input().split())
if a == b:
print(1)
else:
# 在 [0, sqrt(b)] 范围内搜索solution
count = 0
for i in range(a, int(math.sqrt(b)) + 1, a):
if b % i == 0:
count += 1
# 不需要处理对称情况,因为对称的时候输入也必然是相同的,这时输出也是1
print(count * 2) #直接把count乘以2输出

后来仍然是有错误输出,后来把代码放在python调试器调试了一下,发现输入是1000和10000的时候,输出居然是0!这不可能!
才发现我们之前的逻辑有问题(我们应该在ab的范围进行搜索,而不是b)。因此我们之前提出的方法的时间复杂度应该是Oab.

修改后的代码:

import math
a, b = map(int, input().split())
print(f"a={a},b={b}")
if a == b:
print(1)
elif a > b:
print(0)
else:
# 在 [0, sqrt(a * b)] 范围内搜索solution
count = 0
for i in range(a, int(math.sqrt(a*b)) + 1, a):
print(f"i={i},j={(a*b)//i}")
if (a*b) % i == 0:
count += 1
# 不需要处理对称情况,因为对称的时候输入也必然是相同的,这时输出也是1
print(count * 2) #直接把count乘以2输出
#(4pp)

但是这段代码提交后的情况就更糟糕了...仍然有很多错误样例,我们试了一下初始样例3 60发现连初始样例都是错的

a=3,b=60
i=3,j=60
i=60,j=3
i=6,j=30
i=30,j=6
i=9,j=20
i=20,j=9
i=12,j=15
i=15,j=12

所以我们找到一组解以后,还需要二次验证最大公因数是否与原来的a相等!

修改后的代码如下:

import math
a, b = map(int, input().split())
#print(f"a={a},b={b}")
if a == b:
print(1)
elif a > b:
print(0)
else:
# 在 [0, sqrt(a * b)] 范围内搜索solution
count = 0
for i in range(a, int(math.sqrt(a*b)) + 1, a):
if (a*b) % i == 0:
if math.gcd(i,(a*b)//i) == a: #math库中有gcd函数,所以可以直接调用,而不需要我们自己写,虽然自己写也比较容易
#print(f"i={i},j={(a*b)//i}")
#print(f"i={(a*b)//i},j={i}")
count += 1
# 不需要处理对称情况,因为对称的时候输入也必然是相同的,这时输出也是1
print(count * 2) #直接把count乘以2输出

结果:全部accepted! 这题收了~
Current pp: 24pp(+21pp)
Overall pp: 30pp(+21pp)
用时159ms,空间3.75MB

以下是ChatGPT对代码的解析:

你的修改看起来很不错,代码更加简洁,并且考虑了对称情况。下面是对你的代码的一些分析:

  1. Input Handling:

    • The input handling is correct, using map(int, input().split()) to get two integers a and b from user input.
  2. Edge Cases:

    • You correctly handle the case where a is equal to b and directly output 1.
    • You also handle the case where a is greater than b and output 0. This is a good consideration for efficiency.
  3. Search Range:

    • You are searching in the range [a, sqrt(a*b)] using range(a, int(math.sqrt(a*b)) + 1, a). This is a valid approach to narrow down the search space.
  4. Counting Logic:

    • The counting logic inside the loop is correct. You check if (a * b) % i == 0 to find a valid pair (i, (a * b) // i) and then use math.gcd to check if the pair has the correct greatest common divisor.
  5. Symmetry Handling:

    • You correctly mention that symmetry handling is not needed in this case since the input is assumed to be different when a is not equal to b. Therefore, you can directly multiply the count by 2 for the final result.
  6. Efficiency:

    • The use of math.gcd from the standard library is a good choice, as it simplifies the code and leverages an optimized implementation.

Overall, your code is clear, concise, and handles the problem requirements appropriately. Good job!

P1051 [NOIP2005 提高组] 谁拿了最多奖学金

题目序号:P1051
Star Rating: 2.43

题目描述

某校的惯例是在每学期的期末考试之后发放奖学金。发放的奖学金共有五种,获取的条件各自不同:

  1. 院士奖学金,每人 8000 元,期末平均成绩高于 80 分(>80),并且在本学期内发表1篇或1篇以上论文的学生均可获得;
  2. 五四奖学金,每人 4000 元,期末平均成绩高于 85 分(>85),并且班级评议成绩高于 80 分(>80)的学生均可获得;
  3. 成绩优秀奖,每人 2000 元,期末平均成绩高于 90 分(>90)的学生均可获得;
  4. 西部奖学金,每人 1000 元,期末平均成绩高于 85 分(>85)的西部省份学生均可获得;
  5. 班级贡献奖,每人 850 元,班级评议成绩高于 80 分(>80)的学生干部均可获得;

只要符合条件就可以得奖,每项奖学金的获奖人数没有限制,每名学生也可以同时获得多项奖学金。例如姚林的期末平均成绩是 87 分,班级评议成绩 82 分,同时他还是一位学生干部,那么他可以同时获得五四奖学金和班级贡献奖,奖金总数是 4850 元。

现在给出若干学生的相关数据,请计算哪些同学获得的奖金总数最高(假设总有同学能满足获得奖学金的条件)。

输入格式

第一行是1个整数 N,表示学生的总数。

接下来的 N 行每行是一位学生的数据,从左向右依次是姓名,期末平均成绩,班级评议成绩,是否是学生干部,是否是西部省份学生,以及发表的论文数。姓名是由大小写英文字母组成的长度不超过 20 的字符串(不含空格);期末平均成绩和班级评议成绩都是 0100 之间的整数(包括 0100);是否是学生干部和是否是西部省份学生分别用 1 个字符表示,Y 表示是,N 表示不是;发表的论文数是 010 的整数(包括 010)。每两个相邻数据项之间用一个空格分隔。

输出格式

3 行。

  • 1 行是获得最多奖金的学生的姓名。如果有两位或两位以上的学生获得的奖金最多,输出他们之中在输入文件中出现最早的学生的姓名。
  • 2 行是这名学生获得的奖金总数。
  • 3 行是这 N 个学生获得的奖学金的总数。

样例 #1

样例输入 #1

4
YaoLin 87 82 Y N 0
ChenRuiyi 88 78 N Y 1
LiXin 92 88 N N 0
ZhangQin 83 87 Y N 1

样例输出 #1

ChenRuiyi
9000
28700

提示

【数据范围】

对于 100% 的数据,满足 1N100

【题目来源】

NOIP 2005 提高组第一题

为什么我们要做这道题呢?实际上我们要通过这道题学会多行输入结构的处理方式!

这种题实际上可以视为一个简单的单行输入程序运行4次,然后对结果取最早出现的最大值。

首先我们要正确读取输入

i = int(input()) #输入的第一行
while i > 0:
name, examgrade, rategrade, union, west, essays = list(map(input).split())
# 数据预处理
examgrade = int(examgrade)
rategrade = int(rategrade)
essays = int(essays)
union = True if union == 'Y' else 'N'
west = True if west == 'Y' else 'N'

然后我们看题中如何计算奖学金——

  1. 院士奖学金,每人 8000 元,期末平均成绩高于 80 分(>80),并且在本学期内发表1篇或1篇以上论文的学生均可获得;
    那这个条件就是
examgrade > 80 and essays >= 1
  1. 五四奖学金,每人 4000 元,期末平均成绩高于 85 分(>85),并且班级评议成绩高于 80 分(>80)的学生均可获得;
    可以表示为
examgrade > 85 and rategrade > 80
  1. 成绩优秀奖,每人 2000 元,期末平均成绩高于 90 分(>90)的学生均可获得;
  2. 西部奖学金,每人 1000 元,期末平均成绩高于 85 分(>85)的西部省份学生均可获得;
  3. 班级贡献奖,每人 850 元,班级评议成绩高于 80 分(>80)的学生干部均可获得;
    这三个条件分别可以表示为
examgrade > 90
examgrade > 85 and west
#examgrade > 80 and union(一开始看错了)
rategrade > 80 and union

题中说到,只要符合条件就可以得奖,每项奖学金的获奖人数没有限制,每名学生也可以同时获得多项奖学金。
所以直接相加就可以,不需要考虑互斥的情况。
另外,注意到其中有的条件可以合并起来一起讨论,所以在实际写代码的时候可以简化一下代码逻辑:

i = int(input()) #输入的第一行
sum_cash = 0
max_cash = 0
max_name = ''
while i > 0:
name, examgrade, rategrade, union, west, essays = input().split()
examgrade, rategrade, essays = map(int, (examgrade, rategrade, essays))
union = True if union == 'Y' else False
west = True if west == 'Y' else False
# 这里用ChatGPT优化了一下,可以先用input.split()进行输入,然后对数据进行预处理,区分不同类型
cash = 0 #获得的奖学金
if examgrade > 90:
cash += 2000
if examgrade > 85:
if rategrade > 80: cash += 4000
if west: cash += 1000
if examgrade > 80 and essays >= 1:
cash += 8000
if rategrade > 80 and union: #这里一开始看错了,所以把这个条件和上面那个条件用嵌套条件合并起来了,实际上这样的话就只能一个一个判断了
cash += 850
if cash > max_cash: #不是大于等于,是因为输出的是最早出现的名字
max_name, max_cash = name, cash
sum_cash += cash
i -= 1
print(f"{max_name}\n{max_cash}\n{sum_cash}")

本以为能一次accepted,但是发现有很多错误样例!
我发现Wrong Answer都在第三行出现,也就是输出sum_cash的部分,这是怎么回事呢?
原来是我看错了一个条件!

Nice,全部Accepted啦~
3.77MB, 159ms, 23pp到手啦~

Current: 23pp(+23pp)
Overall: 51pp(+21pp)

P1102 A-B 数对

Star Rating: 3.43

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 C,要求计算出所有满足 AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N,C

第二行,N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 AB=C 的数对的个数。

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75% 的数据,1N2000

对于 100% 的数据,1N2×1050ai<2301C<230

2017/4/29 新添数据两组

Solution

首先我们能够想到一种O(n2)的方法,就是遍历其中的每个数对,那么这样需要验证运算的次数就是n(n1)2次。而N的上限有2×105,所以这样显然不可取。
那么还有其他方法吗?
题目当中没有说输入的正整数是排过顺序的,所以需要对输入的N个正整数进行排序

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序

注意列表元素(可变长度)的数据是如何输入的!
你在处理输入的时候,有一个小错误。map函数应该接收一个函数和一个可迭代对象,但是在代码中你使用了 input.split() 而不是 input().split()

OK, 那么到这一步以后,所有的整数就按顺序排好啦~
接下来我们要如何做呢?
比如说,输入是

4
1 1 2 3

排序后的列表为[1,1,2,3]。(注意到数据排序是一个常见的预处理技巧),接下来,如何找到A - B = 1的所有数对个数呢?
在这里,数对显然有[1,2],[1,2]和[2,3]这三个,所以输出是3。

ChatGPT不小心把答案打出来了,让我们看看它的思路吧!

count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left, right = 0, n - 1 # 左右两个指针
while left < right:
diff = sorted_list[right] - sorted_list[left]
if diff == c:
count_pairs += 1
left += 1
right -= 1
elif diff < c:
left += 1
else:
right -= 1
print(count_pairs)

对上面的列表[1,1,2,3]来说,运行一遍上面的算法,但是只能输出一个pairs(写出运行过程后,发现只能找到其中的一个[1,2],而不能找到[1,3]和[2,3])
所以我们要想一个新的思路:

比如说,我们只固定数对中较小的元素,去寻找较大的元素,这样是否能有思路呢?
对于[1,1,2,3] ->
(1)指针指向第一个"1",寻找2的总个数,只有一个"2",那么count+1
(2)指针指向第二个"1",寻找2的总个数,只有一个"2",那么count+1
(3)指针指向"2",寻找3的总个数,只有一个"3",那么count+1
(4)指针指向"3",寻找4的总个数,没有"4"出现
那么结果就是1.
基于此我们可以写一个初版的基于排序后列表进行寻找的代码:

count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
for i in range (len(int_list)):
for j in range(i,len(int_list)): #实际内层循环只会执行很少次数
if int_list[j] == int_list[i] + c:
count_pairs = 0
if int_list[j] > int_list[i] + c:
break #是因为这里强制break导致的
print(count_pairs)

那么这段代码真的能达成我们理想的效果吗?

去调试了一下,发现对于基准样例是正确的

4 1
1 1 2 3
3

那么,还有办法优化吗?比如说如果这里输入的"1"有很多的话,那么这种方法会显著影响效率(即时间复杂度接近O(n2)
因此我们首先需要统计"1"出现的个数,然后统计"2"出现的个数,这两个出现个数的乘积就是这两个数组成数对的总个数!

count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
for i in range (len(int_list)):
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
for j in range(i,len(int_list)): #实际内层循环只会执行很少次数
if int_list[j] == int_list[i]:
i_increment += 1 #定义下一次迭代的时候i移动的步数
if int_list[j] == int_list[i] + c:
target_plusc += 1
if int_list[j] > int_list[i] + c:
break #是因为这里强制break导致的
count_pairs += i_increment * target_plusc
i = i + i_increment - 1 #减1是因为循环步长还会把1加回来
print(count_pairs)

接下来我们尝试对上面的代码进行分析,看起来它的复杂度是O(n2),但事实上这种处理会让复杂度比O(n2)小很多!
假如数据的数字中,相同元素出现的很多,那么代码复杂度应该是O(a2),其中a是列表中不同元素的个数,根据条件应该有 an ;
假如数据的数字中,大多数数字都是不同的,那么代码复杂度应该接近O(n),因为此时内层循环只会执行很少的次数就会跳出循环。

完整代码:

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序
count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
for i in range (len(int_list)):
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
for j in range(i,len(int_list)): #实际内层循环只会执行很少次数
if int_list[j] == int_list[i]:
i_increment += 1 #定义下一次迭代的时候i移动的步数
if int_list[j] == int_list[i] + c:
target_plusc += 1
if int_list[j] > int_list[i] + c:
break #是因为这里强制break导致的
count_pairs += i_increment * target_plusc
i = i + i_increment - 1 #减1是因为循环步长还会把1加回来
print(count_pairs)

然而我发现它对基本用例的输出却是错误的...

4 1
1 1 2 3
输出:7

于是我发现了循环当中的一点小错误,把它稍微修改了一下:

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序
count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
for i in range (len(int_list) - 1):
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
for j in range(i + 1,len(int_list)): #实际内层循环只会执行很少次数
if int_list[j] == int_list[i]:
i_increment += 1 #定义下一次迭代的时候i移动的步数
if int_list[j] == int_list[i] + c:
target_plusc += 1
if int_list[j] > int_list[i] + c:
break #是因为这里强制break导致的
count_pairs += i_increment * target_plusc
i = i + i_increment - 1 #减1是因为循环步长还会把1加回来
print(count_pairs)

这样的话,就接近我们所说的思路啦~!
不过,我们发现测试用例的输出是4,而不是3,为什么呢?
加上调试信息,我们发现:

4 1
1 1 2 3
i=0,int_list[0]=1 Solutions:2
i=1,int_list[1]=1 Solutions:1
i=2,int_list[2]=2 Solutions:1
4

变量i并没有在第一步从i=0直接跳到i=2(因为前两个元素都是1,理论上按照上面的算法会直接跳过第二个"1",但是这里并没有,后来ChatGPT解释了一下,以下是它给出的原因:
在Python中,for i in range(len(int_list))中的i是一个局部变量,其值在每次迭代中都会被重新赋值。在循环体内对i的修改不会影响到for循环的迭代。

在你的代码中,你使用了i = i + i_increment - 1来更新i的值,但这个更新对于for i in range(len(int_list))没有影响。for循环在每次迭代时会重新获取range中的下一个值,而不受循环体内的修改影响。

如果你想在迭代过程中修改i的值,可以考虑使用while循环,这样你可以完全掌控i的变化。

好的~ 那么,接下来我们的代码就可以这样修改一下!

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序
count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
i = 0
while i < len(int_list) - 1:
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
j = i + 1 #j的迭代起点
#print(f"i={i},int_list[{i}]={int_list[i]}",end = " ")
while j < len(int_list): #遍历搜索所有其他相同的A和A+C的个数
if int_list[j] == int_list[i]:
i_increment += 1
if int_list[j] == int_list[i] + c:
target_plusc += 1
if int_list[j] > int_list[i] + c:
break
j += 1
#print(f"Solutions:{i_increment * target_plusc}")
count_pairs += i_increment * target_plusc
i = i + i_increment
print(count_pairs)

不过我们还忽略了另一种情况,也会导致时间复杂度接近O(n2),那就是C很大,N也很大的时候。
这时我们要如何处理呢?
有了!我们先检查最后一个元素是否大于int_list[i]+c,如果最后一个元素不能满足要求,那也会Break!

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序
count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
i = 0
while i < len(int_list) - 1:
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
j = i + 1 #j的迭代起点
#print(f"i={i},int_list[{i}]={int_list[i]}",end = " ")
while j < len(int_list): #遍历搜索所有其他相同的A和A+C的个数
if j == i + 1 and int_list[-1] < int_list[i] + c:
break
if int_list[j] == int_list[i]:
i_increment += 1
if int_list[j] == int_list[i] + c:
target_plusc += 1
if int_list[j] > int_list[i] + c:
break
j += 1
#print(f"Solutions:{i_increment * target_plusc}")
count_pairs += i_increment * target_plusc
i = i + i_increment
print(count_pairs)

这样能解决时间复杂度大的问题吗?让我们拭目以待吧!
确实可以!两个测试用量大的例子都在1s内解决了..不过问题是..为什么只有一个测试用例的输出是正确的呢?

因为我们要对sorted_list进行处理呀!我的天!(我居然现在才发现)

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序
count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
i = 0
while i < len(sorted_list) - 1:
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
j = i + 1 #j的迭代起点
#print(f"i={i},sorted_list[{i}]={sorted_list[i]}",end = " ")
while j < len(sorted_list): #遍历搜索所有其他相同的A和A+C的个数
if j == i + 1 and sorted_list[-1] < sorted_list[i] + c:
break
if sorted_list[j] == sorted_list[i]:
i_increment += 1
if sorted_list[j] == sorted_list[i] + c:
target_plusc += 1
if sorted_list[j] > sorted_list[i] + c:
break
j += 1
#print(f"Solutions:{i_increment * target_plusc}")
count_pairs += i_increment * target_plusc
i = i + i_increment
print(count_pairs)

Nice~ 分数一下子就到84了!不过那两个很大的测试用例列表再次开始TLE了..(用时约为1.2sec)

那么还有什么方法进行优化呢?
也许我们可以优化查找的方式!用二分查找而不是顺序查找
(1)先检查sorted_list[-1]的值,如果sorted_list[-1]小于sorted_list[i]+c,那么这个元素的solution为0.
不过,如果这个元素的solution为0的话,后边的元素都是比这个元素大的,那么....
哈哈哈哈~ 之后的元素肯定也找不到配对的啦~

那么我们就可以把这个判断条件移到外层循环啦~

n,c = map(int,input().split()) #输入N和C
#int_list = list(map(int(input.split())))
int_list = list(map(int, input().split()))
#希望处理后, int_list是一个包含n个整数的列表
sorted_list = sorted(int_list) # 使用 sorted() 对整数列表排序
count_pairs = 0 # 用于统计满足 A - B = C 的数对的个数
left = 0 # 定义一个指针用于固定数对中较小的值
i = 0
while i < len(sorted_list) - 1:
i_increment = 1 #i_increment是相同的A的个数
target_plusc = 0 #target_plusc是相同的A+C的个数
j = i + 1 #j的迭代起点
#print(f"i={i},sorted_list[{i}]={sorted_list[i]}",end = " ")
if sorted_list[-1] < sorted_list[i] + c: #把这个判断移动到了外层循环!~
break
while j < len(sorted_list): #遍历搜索所有其他相同的A和A+C的个数
if sorted_list[j] == sorted_list[i]:
i_increment += 1
if sorted_list[j] == sorted_list[i] + c:
target_plusc += 1
if sorted_list[j] > sorted_list[i] + c:
break
j += 1
#print(f"Solutions:{i_increment * target_plusc}")
count_pairs += i_increment * target_plusc
i = i + i_increment
print(count_pairs)

(2)如果最后一个元素大于sorted_list[i]+c,那么在内层循环开始二分查找,范围是[i+1,len(sorted_list)-1].
(3)如果找到这个元素的话,那么从这个元素开始往左右延伸,找到所有这个元素出现的个数;
(4)如果没找到这个元素(指二分搜索到最后都没找到),那么直接break.

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
left = 0
i = 0
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
j = i + 1
if sorted_list[-1] < sorted_list[i] + c:
break
# 二分查找
low, high = i + 1, len(sorted_list) - 1
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
target_plusc = 1 # 找到了目标元素
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
if target_plusc: # 如果找到目标元素,继续往左右延伸找到所有相同元素的个数
left_index = mid - 1
while left_index >= i + 1 and sorted_list[left_index] == sorted_list[i] + c:
i_increment += 1
left_index -= 1
right_index = mid + 1
while right_index <= len(sorted_list) - 1 and sorted_list[right_index] == sorted_list[i] + c:
i_increment += 1
right_index += 1
count_pairs += i_increment * target_plusc
i = i + i_increment
print(count_pairs)

然而,这个代码在测试用例上面i的跳转出现了问题,仍然是"1"输出了两次!

4 1
1 1 2 3
i=0,sorted_list[0]=1 Solutions:1
i=1,sorted_list[1]=1 Solutions:1
i=2,sorted_list[2]=2 Solutions:1
3

原因是i_increment在每一次迭代都要计算,而上面的代码只有在找到solution的时候才会计算i_iteration,因此会出问题
基于此可以进行下面的修改:不仅采用二分查找的方式找到所有的元素A+C,而且要采用二分查找找到最后一个元素"A"的位置,然后将这个位置+1赋给i.

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
left = 0
i = 0
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
j = i + 1
if sorted_list[-1] < sorted_list[i] + c:
break
# 二分查找最后一个元素A
low, high = i + 1, len(sorted_list) - 1
next_i = i + 1
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid+1] != sorted_list[i]:
next_i = mid+1 # 找到了目标元素
break
elif sorted_list[mid+1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i #计算i_increment
# 二分查找A+C
low, high = i + 1, len(sorted_list) - 1
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
target_plusc = 1 # 找到了目标元素
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
if target_plusc: # 如果找到目标元素,继续往左右延伸找到所有相同元素的个数
left_index = mid - 1
while left_index >= i + 1 and sorted_list[left_index] == sorted_list[i] + c:
left_index -= 1
right_index = mid + 1
while right_index <= len(sorted_list) - 1 and sorted_list[right_index] == sorted_list[i] + c:
right_index += 1
count_pairs += i_increment * target_plusc
i = next_i #将next_i赋值给i
print(count_pairs)

结果输出是这样的:

4 1
1 1 2 3
i=0,sorted_list[0]=1 Solutions:2
i=2,sorted_list[2]=2 Traceback (most recent call last):
File "c:\Users\HUAWEI\Desktop\Review Progress\Tuffy's Review 2023_1\P1102.py", line 26, in <module>
elif sorted_list[mid+1] == sorted_list[i]:
IndexError: list index out of range

mid+1的边界条件没处理好而已..小问题
为此先检查最后一个元素是不是A,如果是A的话,那么就不需要查找了,如果最后一个元素不是A,那么从索引区间i + 1, len(sorted_list) - 2区间开始二分查找!

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
left = 0
i = 0
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
j = i + 1
print(f"i={i},sorted_list[{i}]={sorted_list[i]}",end = " ")
if sorted_list[-1] < sorted_list[i] + c:
print("Out of iterations!")
break
# 二分查找最后一个元素A
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else: #如果最后一个元素大于A,此时需要二分查找
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid+1] != sorted_list[i]:
next_i = mid+1 # 找到了目标元素
break
elif sorted_list[mid+1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i #计算i_increment
# 从第一个不同的元素开始,二分查找A+C
low, high = next_i, len(sorted_list) - 1
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
target_plusc = 1 # 找到了目标元素
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
if target_plusc: # 如果找到目标元素,继续往左右延伸找到所有相同元素的个数
left_index = mid - 1
while left_index >= i + 1 and sorted_list[left_index] == sorted_list[i] + c:
left_index -= 1
right_index = mid + 1
while right_index <= len(sorted_list) - 1 and sorted_list[right_index] == sorted_list[i] + c:
right_index += 1
print(f"Solutions:{i_increment * target_plusc}")
count_pairs += i_increment * target_plusc
i = next_i #将next_i赋值给i
print(count_pairs)

然而,这段代码仍然出现了一些错误...后来多测试了几个例子,发现target_plusc并没有被计算出来...

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
left = 0
i = 0
next_i = 1 #在i循环外维护一个next_i变量
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
j = i + 1
#print(f"i={i},sorted_list[{i}]={sorted_list[i]}",end = " ")
if sorted_list[-1] < sorted_list[i] + c:
#print("Out of iterations!")
break
next_i = i + 1 #将next_i修改为i+1
# 二分查找最后一个元素A
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else: #如果最后一个元素大于A,此时需要二分查找
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid+1] != sorted_list[i]:
next_i = mid+1 # 找到了目标元素
break
elif sorted_list[mid+1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i #计算i_increment
# 二分查找A+C,可以从next_i这个索引开始查找,因为前边的元素都是A,不可能有A+C出现
low, high = next_i, len(sorted_list) - 1
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
target_plusc = 1 # 找到了目标元素
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
if target_plusc: # 如果找到目标元素,继续往左右延伸找到所有相同元素的个数
left_index = mid - 1
while left_index >= next_i and sorted_list[left_index] == sorted_list[i] + c:
left_index -= 1
right_index = mid + 1
while right_index <= len(sorted_list) - 1 and sorted_list[right_index] == sorted_list[i] + c:
right_index += 1
#print(f"right_idx = {right_index},left_idx = {left_index}")
target_plusc = right_index - left_index - 1 #计算target_plusc,因为循环结束后,right_index和left_index指向的是第一个向左/右扩展后不等于A+C的数字的索引,所以这里计算的时候是减1,而不是加1.
#print(f"Solutions:{i_increment * target_plusc}")
count_pairs += i_increment * target_plusc
i = next_i #将next_i赋值给i
print(count_pairs)

Nice~ 现在我们已经拿到了92分~不过仍然不是Accepted,因为仍然有一个测试用例是TLE状态!

再想一想..什么情况下会增大总体时间开销呢?
想一下这个测试用例:

200 5
1 6 6 6 6... (1996)

也就是说,我们二分搜索以后计算target_plusc的方式也不能采用顺序查找的方式!

那么对应的改进思路如下:
在找到一个A+C的索引后:
(1)以next_i和当前索引为区间进行二分查找,找到第一个A+C的索引;
(2)以当前索引和最后一个元素为区间进行二分查找,找到最后一个A+C的索引;

那么...接下来...

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
for i in range(len(sorted_list) - 1):
i_increment = 1
target_plusc = 0
if sorted_list[-1] < sorted_list[i] + c:
break
next_i = i + 1
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else:
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid + 1] != sorted_list[i]:
next_i = mid + 1
break
elif sorted_list[mid + 1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i
low, high = next_i, len(sorted_list) - 1
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
target_plusc = 1
# 以next_i和当前索引为区间进行二分查找,找到第一个A+C的索引
left_index = next_i
left_low, left_high = next_i, i - 1
while left_low <= left_high:
left_mid = (left_low + left_high) // 2
if sorted_list[left_mid] == sorted_list[i] + c:
left_index = left_mid
left_high = left_mid - 1
else:
left_low = left_mid + 1
# 以当前索引和最后一个元素为区间进行二分查找,找到最后一个A+C的索引
right_index = i
right_low, right_high = i + 1, len(sorted_list) - 1
while right_low <= right_high:
right_mid = (right_low + right_high) // 2
if sorted_list[right_mid] == sorted_list[i] + c:
right_index = right_mid
right_low = right_mid + 1
else:
right_high = right_mid - 1
target_plusc = right_index - left_index + 1
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
count_pairs += i_increment * target_plusc
print(count_pairs)

又对代码做了亿点点优化...

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
left = 0
i = 0
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
j = i + 1
#print(f"i={i},sorted_list[{i}]={sorted_list[i]}",end = " ")
if sorted_list[-1] < sorted_list[i] + c:
#print("Out of iterations!")
break
next_i = i + 1 #初始化next_i
# 二分查找最后一个元素A
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else: #如果最后一个元素大于A,此时需要二分查找
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid+1] != sorted_list[i]:
next_i = mid+1 # 找到了目标元素
break
elif sorted_list[mid+1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i #计算i_increment
# 二分查找A+C,可以从next_i这个索引开始查找,因为前边的元素都是A,不可能有A+C出现
low, high = next_i, len(sorted_list) - 1
#print(f"next_i = {next_i}")
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
#也要处理一种边界情况,就是left_low和next_i相等的情况!
if sorted_list[next_i] == sorted_list[i] + c: #还是要检查边界条件..
left_index = next_i
# 以next_i和当前索引为区间进行二分查找,找到第一个A+C的索引
else:
left_low, left_high = next_i, mid
while left_low <= left_high:
#print(f"left_low ={left_low}, left_high={left_high}")
left_mid = (left_low + left_high) // 2
if sorted_list[left_mid] == sorted_list[i] + c and sorted_list[left_mid-1] < sorted_list[i] + c:
left_index = left_mid
break #这里要加break...有点着急忘了..后面right也是...如果我要是紧张的话真的什么都可能想不出来!
elif sorted_list[left_mid-1] == sorted_list[i] + c:
left_high = left_mid - 1
else:
left_low = left_mid + 1 #这里条件也写反了
# 以当前索引和最后一个元素为区间进行二分查找,找到最后一个A+C的索引
right_index = i
if sorted_list[-1] == sorted_list[i] + c: #还是要检查边界条件..
right_index = len(sorted_list) - 1
else:
right_low, right_high = mid, len(sorted_list) - 2
while right_low <= right_high:
#print(f"right_low ={right_low}, right_high={right_high}")
right_mid = (right_low + right_high) // 2
if sorted_list[right_mid] == sorted_list[i] + c and sorted_list[right_mid + 1] > sorted_list[i] + c:
right_index = right_mid
break
elif sorted_list[right_mid+1] == sorted_list[i] + c:
right_low = right_mid + 1
else:
right_high = right_mid - 1 #这里条件写反了...可以脑子里过一遍查找的过程..
#print(f"right_index ={right_index}, left_index={left_index}")
target_plusc = right_index - left_index + 1 #计算target_plusc,因为循环结束后,right_index和left_index指向的是第一个向左/右扩展后不等于A+C的数字的索引,所以这里计算的时候是减1,而不是加1.
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
count_pairs += i_increment * target_plusc
#print(f"Solutions:{i_increment * target_plusc}, i_increment={i_increment}, target_plusc={target_plusc}")
i = next_i #将next_i赋值给i
print(count_pairs)

然而,这段代码只得到了84分!
我们能否再缩小这个A+C的搜索范围呢?因为我们知道二分查找中,left和right的更新只发生在mid索引不等于目标值的情况下
我们可以把搜索范围缩小到外层二分搜索的left到right区间,边界条件也修改成left和right的索引。
(这里可以思考一下上面说的是不是对的)
因此,代码可以这样修改:

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
left = 0
i = 0
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
j = i + 1
#print(f"i={i},sorted_list[{i}]={sorted_list[i]}",end = " ")
if sorted_list[-1] < sorted_list[i] + c:
#print("Out of iterations!")
break
next_i = i + 1 #初始化next_i
# 二分查找最后一个元素A
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else: #如果最后一个元素大于A,此时需要二分查找
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid+1] != sorted_list[i]:
next_i = mid+1 # 找到了目标元素
break
elif sorted_list[mid+1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i #计算i_increment
# 二分查找A+C,可以从next_i这个索引开始查找,因为前边的元素都是A,不可能有A+C出现
low, high = next_i, len(sorted_list) - 1
#print(f"next_i = {next_i}")
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] + c:
#也要处理一种边界情况,就是left_low和next_i相等的情况!
if sorted_list[low] == sorted_list[i] + c: #还是要检查边界条件..
left_index = low
# 以next_i和当前索引为区间进行二分查找,找到第一个A+C的索引
else:
left_low, left_high = low + 1, mid #我们可以以left为左边界,对应上边的边界条件也可以换一下
while left_low <= left_high:
#print(f"left_low ={left_low}, left_high={left_high}")
left_mid = (left_low + left_high) // 2
if sorted_list[left_mid] == sorted_list[i] + c and sorted_list[left_mid-1] < sorted_list[i] + c:
left_index = left_mid
break #
elif sorted_list[left_mid-1] == sorted_list[i] + c:
left_high = left_mid - 1
else:
left_low = left_mid + 1 #
# 以当前索引和最后一个元素为区间进行二分查找,找到最后一个A+C的索引
right_index = i
if sorted_list[high] == sorted_list[i] + c: #
right_index = high
else:
right_low, right_high = mid, high - 1
while right_low <= right_high:
#print(f"right_low ={right_low}, right_high={right_high}")
right_mid = (right_low + right_high) // 2
if sorted_list[right_mid] == sorted_list[i] + c and sorted_list[right_mid + 1] > sorted_list[i] + c:
right_index = right_mid
break
elif sorted_list[right_mid+1] == sorted_list[i] + c:
right_low = right_mid + 1
else:
right_high = right_mid - 1 #
#print(f"right_index ={right_index}, left_index={left_index}")
target_plusc = right_index - left_index + 1 #
break
elif sorted_list[mid] < sorted_list[i] + c:
low = mid + 1
else:
high = mid - 1
count_pairs += i_increment * target_plusc
#print(f"Solutions:{i_increment * target_plusc}, i_increment={i_increment}, target_plusc={target_plusc}")
i = next_i #将next_i赋值给i
print(count_pairs)

有一说一...又回到了92分的状态,第四个测试样例是什么呢...

看起来这个优化步骤已经走不通了....

那么接下来要使用最后的杀手锏了——我们要用一个字典来统计每个元素出现的个数,然后进行输出,这样只需要对原来的数据进行一次遍历操作(复杂度为O(N))

大体步骤如下:
遍历排序后的list,输出一个字典,其key为整数,对应值为这个整数元素出现的个数,然后遍历这个字典即可。(这样,每个元素最多只会出现一次),然后用之前的二分查找方法查找A+C即可,读取A的个数和A+C的个数相乘添加到我们要计算的总和上面

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
i = 0
# 统计每个元素的个数
element_counts = {}
for num in sorted_list:
if num in element_counts:
element_counts[num] += 1
else:
element_counts[num] = 1
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
if sorted_list[-1] < sorted_list[i] + c:
break
next_i = i + 1
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else:
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid + 1] != sorted_list[i]:
next_i = mid + 1
break
elif sorted_list[mid + 1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i
# 在字典中查找键A+C对应的位置
key_to_find = sorted_list[i] + c
if key_to_find in element_counts:
target_plusc = element_counts[key_to_find]
count_pairs += i_increment * target_plusc
i = next_i
print(count_pairs)

在这个思路指引下,第二个测试用例快了近一倍,但是第四个测试用例仍然消耗了1.2sec的时间..
所以我就在想是不是排序算法问题...

换了个归并排序的算法,再试一下!

def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
return merge(left_half, right_half)
def merge(left, right):
result = []
left_index, right_index = 0, 0
while left_index < len(left) and right_index < len(right):
if left[left_index] < right[right_index]:
result.append(left[left_index])
left_index += 1
else:
result.append(right[right_index])
right_index += 1
result.extend(left[left_index:])
result.extend(right[right_index:])
return result
n, c = map(int, input().split())
int_list = list(map(int, input().split()))
# 使用归并排序替代 sorted 函数
sorted_list = merge_sort(int_list)
count_pairs = 0
i = 0
# 统计每个元素的个数
element_counts = {}
for num in sorted_list:
if num in element_counts:
element_counts[num] += 1
else:
element_counts[num] = 1
while i < len(sorted_list) - 1:
i_increment = 1
target_plusc = 0
if sorted_list[-1] < sorted_list[i] + c:
break
next_i = i + 1
if sorted_list[-1] == sorted_list[i]:
next_i = len(sorted_list)
else:
low, high = i + 1, len(sorted_list) - 2
while low <= high:
mid = (low + high) // 2
if sorted_list[mid] == sorted_list[i] and sorted_list[mid + 1] != sorted_list[i]:
next_i = mid + 1
break
elif sorted_list[mid + 1] == sorted_list[i]:
low = mid + 1
else:
high = mid - 1
i_increment = next_i - i
# 在字典中查找键A+C对应的位置
key_to_find = sorted_list[i] + c
if key_to_find in element_counts:
target_plusc = element_counts[key_to_find]
count_pairs += i_increment * target_plusc
i = next_i
print(count_pairs)

然而这并没好起来...这是什么原因呢?
我才发现ChatGPT的代码中,对于i的迭代仍然迭代的是原来的列表,而不是新生成的字典!也许是这个原因..
不过,ChatGPT是这样回答的——
在 Python 3.7+ 中,字典(dict)开始保持元素的插入顺序。这意味着从 Python 3.7 版本开始,字典会按照元素插入的顺序迭代元素。在更早的 Python 版本中,字典是无序的。

所以,如果你的 Python 版本是 3.7 及以上,你可以依赖字典的顺序性,无需担心元素顺序的问题。在你的代码中,通过遍历字典,你已经在按顺序访问元素。

然而,请注意,虽然 Python 3.7+ 以后字典保持插入顺序,但这个特性并没有写入官方 Python 语言规范(PEP 468 中有相关描述)。所以,尽管很多 Python 实现都支持这个特性,但你在编写代码时最好不要过度依赖字典的顺序性。

因此,需要换个思路...

如果用一个二元组列表的话,会怎么样呢?

n, c = map(int, input().split())
int_list = list(map(int, input().split()))
sorted_list = sorted(int_list)
count_pairs = 0
i = 0
# 初始化二元组列表
element_counts = [(sorted_list[0], 1)]
for i in range(1, len(sorted_list)):
if sorted_list[i] == sorted_list[i - 1]:
# 如果当前元素与前一个元素相同,增加计数
element_counts[-1] = (sorted_list[i], element_counts[-1][1] + 1)
else:
# 如果当前元素与前一个元素不同,添加新的二元组
element_counts.append((sorted_list[i], 1))
#print(element_counts)
i = 0 #这里再加一个i = 0,因为我们发现后边循环的i和前边循环的i用的居然是同一个地址的变量!
while i < len(element_counts):
#print(f"i={i},element_counts[{i}][0]={element_counts[i][0]}",end = " ")
i_increment = element_counts[i][1] # 原来的 i_increment 表示 A 的个数
target_plusc = 0 # 原来的 target_plusc 表示 A+C 的个数
if element_counts[-1][0] < element_counts[i][0] + c:
#print("Out of iterations!")
break
# 在二元组列表中查找键 A+C 对应的位置
key_to_find = element_counts[i][0] + c
low, high = i+1, len(element_counts) - 1
mid = (low + high) // 2
while low <= high:
mid = (low + high) // 2
if element_counts[mid][0] == key_to_find:
break
elif element_counts[mid][0] < key_to_find:
low = mid + 1
else:
high = mid - 1
target_plusc = element_counts[mid][1]
#print(f"Solutions:{i_increment * target_plusc}, i_increment={i_increment}, target_plusc={target_plusc}")
count_pairs += i_increment * target_plusc
i += 1
print(count_pairs)

如果我们输出结果:

30 6
1 1 1 1 7 7 7 7 7 7 7 7 13 19 13 19 13 19 19 13 13 19 13 19 25 31 25 31 31 25
[(1, 4), (7, 8), (13, 6), (19, 6), (25, 3), (31, 3)]
i=0,element_counts[0][0]=1 Solutions:32, i_increment=4, target_plusc=8
i=1,element_counts[1][0]=7 Solutions:48, i_increment=8, target_plusc=6
i=2,element_counts[2][0]=13 Solutions:36, i_increment=6, target_plusc=6
i=3,element_counts[3][0]=19 Solutions:18, i_increment=6, target_plusc=3
i=4,element_counts[4][0]=25 Solutions:9, i_increment=3, target_plusc=3
i=5,element_counts[5][0]=31 Out of iterations!
143

Nice,答案是对的!
不过提交的时候发现,仍然会得到错误的结果...

思路已经快枯竭了..这题的Markdown差不多到1000行了..
看了下别人的题解...哇哦好短..(思路来自洛谷用户“樱花飞舞”)

#include<bits/stdc++.h>
using namespace std;
long a[200001];
long N,C,ans;
int main()
{
cin>>N>>C;
for(int i=1;i<=N;i++)
{
cin>>a[i];
}
sort(a+1,a+N+1);
for(int i=1;i<=N;i++)
{
ans+=((upper_bound(a+1,a+N+1,a[i]+C)-a)-(lower_bound(a+1,a+N+1,a[i]+C)-a));
}
cout<<ans;
return 0;
}

让ChatGPT生成了一个Python版本出来

from bisect import bisect_left, bisect_right
def count_pairs_with_difference(arr, target_diff):
arr.sort() # 排序数组
count = 0
for i in range(len(arr)):
left_index = bisect_left(arr, arr[i] + target_diff)
right_index = bisect_right(arr, arr[i] + target_diff)
count += right_index - left_index
return count
if __name__ == "__main__":
N, C = map(int, input().split())
arr = list(map(int, input().split()))
result = count_pairs_with_difference(arr, C)
print(result)

果然这段代码就能过测试!...而且比我们写的代码要快很多!
不过它是一个什么样的原理呢?
下面是ChatGPT的一个解释:
在 Python 中,你可以使用 bisect 模块提供的 bisect_leftbisect_right 函数来实现类似的思路。这两个函数分别用于在有序序列中查找第一个不小于某个值和第一个大于某个值的位置。
在这个例子中,count_pairs_with_difference 函数接受一个数组 arr 和目标差值 target_diff,然后使用 bisect_left 找到第一个不小于 arr[i] + target_diff 的位置,使用 bisect_right 找到第一个大于 arr[i] + target_diff 的位置,最终得到符合条件的 (i, j) 对数量。

这样的实现方式更简洁,并且在 Python 中也可以很方便地进行处理。这段代码的复杂度也是O(NlogN)(因为需要排序,但是处理的时候复杂度就只有O(N))

(我的脑子已经快爆炸了...果然往往这种题有着最朴素的解决方式...)

pp结算:
84%:40pp 92%:47pp 100%:56pp

Current: 56pp(+56pp)
Your Personal-Edit Best Record: 47pp(92%)
Overall: 105pp(+54pp)

本文作者:Yuzu_OvO(喵露露版)

本文链接:https://www.cnblogs.com/yuzusbase/p/17968280

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Yuzu_OvO(喵露露版)  阅读(15)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起