XCPCer速通Python

purinliang發表於2024-04-04

眾所周知C++是幾乎所有XCPCer的主語言,但是有一些時候(比如不想寫大數乘法、在LeetCode上做題、參加筆試面試)不得不使用在某些場景下更好寫的Python。Python的標準庫提供了很多很實用的功能,而且有著非常人性化的異常(比如陣列越界、除以零等),最重要的是它內建的print函式可以非常方便的輸出各種各樣複雜的巢狀結構(C++需要自己寫一個函式進行輸出,或者每次都使用for迴圈等,在LeetCode/筆試面試階段非常不利)。

這篇文章記錄了一些我覺得Python提供了內建的方便的庫函式,並且有一些坑是可能需要注意的也會記錄下來。

輸入

一般是用字串的split(),無參的split會自動過濾各種空白字元,不要畫蛇添足。(注意split(" ")會把"1 2"分割為["1", "", "2"])其返回一個列表,使用列表推導式把他分別賦值給需要的資料型別就可以了。

a, b, c = [int(i) for i in input().split()]    # 一行輸入多個整數
print(a, b, c)      # 1 2 3
print(a + b + c)    # 6
a, b, c = input().split()    # 一行輸入多個字串
print(a, b, c)      # 1 2 3 注意這裡是字串
print(a + b + c)    # 123

出現混合輸入的情況就分別進行轉換即可。

二進位制輸入

a_str = "1001"
a_int = int(a_str, 2)
print(a_int)        # 9
b_int = 12
b_str = bin(b_int)
print(b_str)        # 0b1100
print(b_str[2:])    # 1100

數學

Python中的一些簡單的數學行為和C++的不一樣,要特別注意

INF_float = 1e9
print(INF_float)    # 1000000000.0
INF_int = 10**9
print(INF_int)      # 1000000000

print(5 / 2)    # 2.5
print(5 // 2)   # 2

print(7 % 3)    # 1
print(-7 % 3)   # 2  注意這個和C++的顯著區別,Python的%是會自動變回非負數的

print(5 // 0)   # ZeroDivisionError: integer division or modulo by zero

Python中還有更好用的函式,可以對列表或者列表切片等使用。

my_list = [i for i in range (1, 6)]
print(my_list)          # [1, 2, 3, 4, 5]
print(sum(my_list))     # 15
print(min(my_list))     # 1
print(max(my_list))     # 5

my_list = [1, 1, 2, 2, 3, 3]
print(my_list.index(min(my_list)))  # 0 返回第一個最小值的位置
print(my_list.index(max(my_list)))  # 4 返回第一個最大值的位置

列表 List

https://docs.python.org/zh-cn/3/tutorial/datastructures.html

常用方法

見上面的連結,說得非常清楚,基本上都和C++的vector的方法一模一樣。與眾不同的是:

len(my_list)           # vector.size()
my_list.append(x)      # vector.push_back(x);
x = my_list.pop()      # x = vector.back();    vector.pop_back();
my_list.sort()                # sort(vector.begin(), vector.end()) 原地排序
my_list.sort(reversed=True)   # sort(vector.begin(), vector.end(), greater<int>())
my_list.reverse()             # reverse(vector.begin(), vector.end())

set(my_list)          # 在不修改list的前提下返回一個無重的set,不一定保證有序 https://docs.python.org/zh-cn/3/tutorial/datastructures.html#sets
sorted(my_list)       # 在不修改原本的list的情況下返回一個新的有序list
my_list= list(set(sorted(my_list)))  # 排序並去重

二維列表

生成一個初始值為0的,長度為n的空列表:

n = 10
my_list_1 = [0] * n                 # 如果要生成包含n個空列表的列表,則是[[]] * n
my_list_2 = [0 for _ in range(n)]   # 注意這個列表推導式要用[]括起來才是列表
print(my_list_1, my_list_2)         # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
my_list_1[1] = 1
my_list_2[2] = 2
print(my_list_1, my_list_2)         # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 2, 0, 0, 0, 0, 0, 0, 0]

print(my_list_1[n])                 # IndexError: list index out of range

看起來沒啥問題是吧?看看二維列表

n = 3
my_list_1 = [[0] * n] * n
my_list_2 = [[0 for _ in range(n)] for _ in range(n)]
print(my_list_1, my_list_2)        # [[0, 0, 0], [0, 0, 0], [0, 0, 0]] [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
my_list_1[1][1] = 1
my_list_2[2][2] = 2
print(my_list_1, my_list_2)        # [[0, 1, 0], [0, 1, 0], [0, 1, 0]] [[0, 0, 0], [0, 0, 0], [0, 0, 2]]

用第一種方法生成的列表,其的引用被複制了n次。而對於內建的整數型別0來說,沒有這個問題。意味著如果生成的是引用型別,要注意區分,一般都是應該用第二種方法生成的。

當然由於0沒問題,所以下面的寫法也是可以的

n = 3
my_list_3 = [[0] * n for _ in range(n)]
my_list_4 = [0 for _ in range(n)] * n    # 這種方法生成的是一維列表
print(my_list_3, my_list_4)              # [[0, 0, 0], [0, 0, 0], [0, 0, 0]] [0, 0, 0, 0, 0, 0, 0, 0, 0]
my_list_3[1][1] = 1
my_list_4[2] = 2                   
print(my_list_3, my_list_4)              # [[0, 0, 0], [0, 1, 0], [0, 0, 0]] [0, 0, 2, 0, 0, 0, 0, 0, 0]

當然,為了防止迷惑,我覺得最好統一養成用my_list_2的寫法的習慣。

逆序下標訪問列表

python可以透過負數下標列舉列表:

n = 6
my_list = [i for i in range(n)]
print(my_list)                          # [0, 1, 2, 3, 4, 5]
# 用負數逆序列舉列表
print(my_list[-1], my_list[-2])         # 5 4
# 負0和正0都是0,沒有區別
print(my_list[-0], my_list[+0])         # 0 0
# 要記得逆序的時候,最後一個元素是-n而不是-(n - 1)
print(my_list[-n], my_list[-(n - 1)])   # 0 1

逆序列舉列表

使用range逆序列舉的時候,要寫清楚3個引數。只寫兩個引數的時候,結果如下:

n = 6
my_list = [i for i in range(n)]
print(my_list)                                  # [0, 1, 2, 3, 4, 5]
print([my_list[i] for i in range(n - 1, -1)])   # [] 輸出是空列表
print([i for i in range(n - 1, -1)])            # [] 因為range(n - 1, -1) 是空列表
print([my_list[i] for i in range(-1, -n - 1)])  # [] 輸出是空列表
print([i for i in range(-1, -n - 1)])           # [] 因為range(-1, -n - 1) 是空列表

寫清楚三個引數之後就可以了,我相信在XCPC裡面第二種、第三種寫法是比較常用的。

n = 6
my_list = [i for i in range(n)]
print(my_list)                                      # [0, 1, 2, 3, 4, 5]
print([my_list[i] for i in range(n - 1, -1, -1)])   # [5, 4, 3, 2, 1, 0]
print([my_list[i] for i in range(-1, -n - 1, -1)])  # [5, 4, 3, 2, 1, 0]
print([my_list[i] for i in reversed(range(n))])     # [5, 4, 3, 2, 1, 0] 推薦,生成了逆序的下標i,方便處理

如果不需要用到下標i,則直接生成一個反向的列表就可以了:

n = 6
my_list = [i for i in range(n)]
print(my_list)                                      # [0, 1, 2, 3, 4, 5]
print(my_list[::-1])                                # [5, 4, 3, 2, 1, 0] 要麼只寫最後一個步長-1
print(my_list[-1:-n - 1:-1])                        # [5, 4, 3, 2, 1, 0] 要麼必須寫夠3個引數
print(reversed(my_list))                            # <list_reverseiterator object at 0x7fc4e27655b0>
print([reversed(my_list)])                          # [<list_reverseiterator object at 0x7fc4e27655b0>]

my_list.reverse()                                   # 此方法會修改my_list
print(my_list)                                      # [0, 1, 2, 3, 4, 5]

字串 str

字串的長度,注意寫法和C++的不太一樣

my_str = "Hello world!"
print(my_str)           # Hello world!
print(len(my_str))      # 12
# print(my_str.len)     # 崩潰
# print(my_str.length)  # 崩潰
# print(my_str.size)    # 崩潰

字串也沒有reverse方法,要使用一些奇怪的辦法去實現,一般應該是生成一個反向的字串切片覆蓋到原本的上面

my_str = "Hello world!"
# my_str.reverse()      # 崩潰
# reverse(my_str)       # 崩潰
print(reversed(my_str)) # <reversed object at 0x7fcea5f158b0>
print("".join(reversed(my_str)))    # !dlrow olleH
print(my_str[::-1])                 # !dlrow olleH

字串有find方法,找到第一個匹配的下標的位置,找不到返回-1

my_str = "abcabcabc"
print(my_str.find("abc"))   # 0
print(my_str.find("cab"))   # 2
print(my_str.find("d"))     # -1

字串有count方法,找到所有不重疊的子串出現的次數。

my_str = "abababab"
print(my_str.count("ab"))   # 4
print(my_str.count("abab")) # 2

二分查詢

https://docs.python.org/zh-cn/3.12/library/bisect.html

# bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None)    # 第一個大於等於x的元素,相當於C++的lower_bound
# bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None)   # 第一個大於x的元素,相當於C++的upper_bound
# bisect.insort_left(a, x, lo=0, hi=len(a), *, key=None)    # 插入到第一個大於等於x的元素的左邊
# bisect.insort_right(a, x, lo=0, hi=len(a), *, key=None)   # 插入到第一個大於x的元素的左邊(最後一個小於等於x的元素的右邊)

import bisect

my_list = [1, 2, 3, 4, 4, 4, 4, 5, 6]
print(bisect.bisect_left(my_list, 4))   # 3 第一個4的位置
print(bisect.bisect_right(my_list, 4))  # 7 最後一個4的下一個位置,也就是5
bisect.insort_left(my_list, 4)
print(my_list)      # [1, 2, 3, 4, 4, 4, 4, 4, 5, 6]  
bisect.insort_right(my_list, 7)
print(my_list)      # [1, 2, 3, 4, 4, 4, 4, 4, 5, 6, 7]

更多複雜的判斷可以點進連結自己看。

奇怪的列表列舉 itertools

https://docs.python.org/3/library/itertools.html

import itertools

n = 5
my_list = [i for i in range(1, 1 + n)]
print(my_list)                  # [1, 2, 3, 4, 5]
accumulated_my_list = [i for i in itertools.accumulate(my_list)]
print(accumulated_my_list)      # [1, 3, 6, 10, 15]

對同一個列表還有一些看起來有用的方法:

product: 相當於n維for迴圈的笛卡爾積,可能在Floyd演算法可以用?
permutations: 排列數(按位置區分元素而不是按值區分),可以代替C++的next_permutation使用
combinations: 組合數

import itertools

two_dimension_product = [[i, j] for i, j in itertools.product(range(3), repeat=2)]
print(two_dimension_product)    # [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]

A_3_3 = [l for l in itertools.permutations([1, 2, 3])]      # 位置的排列數,(位置的)字典序
print(A_3_3)        # [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
A_3_2 = [l for l in itertools.permutations([1, 2, 3], 2)]   # 位置的排列數,(位置的)字典序
print(A_3_2)        # [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
C_3_3 = [l for l in itertools.combinations([1, 2, 3], 3)]   # 位置的組合數,(位置的)字典序
print(C_3_3)        # [(1, 2, 3)]
C_3_2 = [l for l in itertools.combinations([1, 2, 3], 2)]   # 位置的組合數,(位置的)字典序
print(C_3_2)        # [(1, 2), (1, 3), (2, 3)]
C_3_3_special = [l for l in itertools.combinations_with_replacement([1, 2, 3], 3)]  # 同一個位置可以重複選的組合數
print(C_3_3_special)    # [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 2), (1, 2, 3), (1, 3, 3), (2, 2, 2), (2, 2, 3), (2, 3, 3), (3, 3, 3)]    
C_3_2_special = [l for l in itertools.combinations_with_replacement([1, 2, 3], 2)]  # 同一個位置可以重複選的組合數
print(C_3_2_special)    # [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]

# 這些方法說的重複是指位置重複,與值是否重複是無關的
A_3_2_replacement = [l for l in itertools.permutations([1, 1, 2], 2)]
print(A_3_2_replacement)    # [(1, 1), (1, 2), (1, 1), (1, 2), (2, 1), (2, 1)]
C_3_2_replacement = [l for l in itertools.combinations([1, 1, 2], 2)]
print(C_3_2_replacement)    # [(1, 1), (1, 2), (1, 2)]

簡單資料結構

棧用list來模仿剛剛好。基本上是完全一致的。

佇列

如果使用內建的雙端佇列,和C++的queue是一樣的體驗。https://docs.python.org/zh-cn/3/library/collections.html#deque-objects

其實列表可以一邊遍歷一邊append,所以也可以這樣寫BFS:

Q = []
vis[1] = True
Q.append(1)
for u in Q:
  for v in G[u]:
    if not vis[v]:
      vis[v] = True
      Q.append(v)

在實現BFS時,C++也可以用vector來代替queue,其實更好寫。寫法和上面相同。

集合

set對應的大概是C++的unordered_set。

詳見:https://docs.python.org/zh-cn/3/library/stdtypes.html#set-types-set-frozenset

對映 / 字典

python的字典對應的大概是C++的unordered_map,不過字典的順序是插入順序。

詳見:https://docs.python.org/zh-cn/3/library/stdtypes.html#mapping-types-dict

相關文章