本系列文章将于2021年整理出版,书名《算法竞赛专题解析》。
前驱教材:《算法竞赛入门到进阶》 清华大学出版社
网购:京东 当当 作者签名书
如有建议,请加QQ 群:567554289,或联系作者QQ:15512356
很多人认为Python是最受欢迎的编程语言,它编码简洁,有强大的库。
Python早已应用在算法竞赛中,能节省大量比赛时间。
大学的某些算法竞赛(ICPC)已经支持用Python语言提交代码。即使比赛不支持用Python提交代码,而Linux系统一般是预装Python的,可以用Python做辅助工作。
Python的优点有编码简便、处理大数非常简单、构造测试数据比C++更简单等。Python的主要问题是执行时间慢,比C++、java慢得多,竞赛队员可能不会直接用Python交题。不过,Python可以作为工具使用,常用的是构造数据、写对拍代码。一个典型的应用是:如果能用打表法交题,可以先用python打表出数据,然后用c++或直接用Python提交。
读者会发现,用Python做这些事,比用C++容易得多,这在紧张的比赛中是很有用的。
若读者想学习用Python写算法竞赛题的代码,建议到力扣网站(leetcode-cn.com)练习,每个题都有人提交Python题解。
Python的强大,主要是因为它庞大的库。Python有两种版本Python 2、Python 3,两者不兼容。本节基于 Python 3。Python编译环境下载地址:https://www.python.org/downloads/
下面介绍Python在算法竞赛中的应用。
下面的题目是计算大数。
阶乘之和 洛谷 P1009https://www.luogu.com.cn/problem/P1009
题目描述:计算S=1!+2!+3!..+n! (n≤50)
计算结果是一个天文数字,如果用C++编码,需要用高精度,很复杂。而Java和python都能直接处理大数。
用Java编码很简单:
import java.math.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in= new Scanner(System.in);
int n = in.nextInt();
BigInteger s = new BigInteger("1");
BigInteger ans = new BigInteger("0");
for (int i = 1; i <= n; i++) {
s = s.multiply(new BigInteger(String.valueOf(i)));
ans = ans.add(s);
}
System.out.println(ans);
}
}
用Python编码更简单:
n = int(input())
s = 1
ans = 0
for i in range(1,n+1,1):
s *= i
ans += s
print(ans)
用Python构造测试数据,比c++简单得多。它能直接产生极大的数字,方便地产生随机字符等。下表列出了一些典型的随机数、随机字符串构造方法1。
(1)导入库
import random
可以写成:
from random import *
此时后面的代码能够简单一点,例如把random.randint直接写为randint
(2)在指定范围内生成一个很大的随机整数:
print (random.randint(-9999999999999999,9999999999999999))
输出示例:428893995939258
(3)在指定范围内(0到100000)生成一个随机偶数:
print (random.randrange(0, 100001, 2))
输出示例:14908
(4)生成一个0到1之间的随机浮点数:
print (random.random())
输出示例:0.2856636141181378
(5)在指定范围内(1到20)生成一个随机浮点数:
print (random.uniform(1, 20))
输出示例:9.81984258258233
(6)在指定字符中生成一个随机字符:
print (random.choice('abcdefghijklmnopqrst@#$%^&*()'))
输出示例:d
(7)在指定字符中生成指定数量的随机字符:
print (random.sample('zyxwvutsrqponmlkjihgfedcba',5))
输出示例:[‘z’, ‘u’, ‘x’, ‘w’, ‘j’]
(8)导入库
import string
若写成from string import *,下面的string.ascii_letters改为ascii_letters
(9)用a-z、A-Z、0-9生成指定数量的随机字符串:
ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 8))
print (ran_str)
输出示例:iCTm6yxN
(10)从多个字符中选取指定数量的字符组成新字符串:
print (''.join(random.sample(['m','l','k','j','i','h','g','d'], 5)))
输出示例:mjlhd
(11)打乱数组的顺序:
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
random.shuffle(items)
for i in range(0,len(items),1): #逐个打印
print (items[i]," ",end='')
输出示例:1 0 8 3 5 7 9 4 6 2
1、整数去重
随机生成的整数,很多是重复的,而一般情况下需要不重复的数据。下面给出两种去重方法。
第1种方法用set(),速度极快。set()的时间复杂度差不多是O(n)的,和生成随机数的时间差不多。set()去重不能保持数据的原顺序。set()去重的原理是hash,所以有时候它返回的结果中部分数据看起来像排过序,但整体上并不是有序的。虽然set()去重后的数据的顺序不是那么随机,但是可以用shuffle()再次把数据打乱,得到排序随机的数组。
第2种方法是暴力去重,非常非常慢,不过它的结果保持了原数据的顺序。
def NonRepeatList1(data): #函数1:set去重,不保持原顺序
return list(set(data))
def NonRepeatList2(data): #函数2:暴力去重,保持原顺序
return [i for n, i in enumerate(data) if i not in data[:n]]
#下面测试上面2个函数
import random
import time
time0 = time.time()
a = []
for i in range(0,100000,1): #10万个随机数
a.append(random.randint(-100000000,100000000)) #随机数取值范围
#print (a) #可以打印数组看看
print ("random time =",time.time()-time0) #统计随机数的生成时间
time0 = time.time()
b = NonRepeatList1(a) #去重,不保持原顺序
#print (b) #打印看看顺序
random.shuffle(b) #再次打乱顺序
#print (b) #打印看看是否乱序
print ("set time =",time.time()-time0) #统计set()去重的时间
time0 = time.time()
c = NonRepeatList2(a) #去重,保持原顺序
#print (c) #打印看看是否保持原序
print ("enum time =",time.time()-time0) #统计暴力去重的时间
代码中统计了两种方法的执行时间,在作者的电脑上运行,10万个数的时间是:
random time = 0.10671424865722656
set time = 0.06288003921508789
enum time = 99.96579337120056
可见set()是极快的,是暴力去重的1600倍。
再试试1000万个数,生成随机数和去重的时间是:
random time = 10.069396495819092
set time = 10.71152925491333
2、小数去重
如果要生成不同的小数,简单的办法是先用上面的代码生成去重整数数组,然后把每个整数除以10的幂次即可。例如生成2位小数,把每个整数除以100。
d = []
for i in range(0,len(b),1): #b是去重后的整数数组
d.append(b[i] / 100)
1、构造测试数据
仍然以Hdu 1425为例。
hdu 1425 sort http://acm.hdu.edu.cn/showproblem.php?pid=1425
给你n个整数,请按从大到小的顺序输出其中前m大的数。
输入:每组测试数据有两行,第一行有两个数n, m(0 < n, m < 1000000),第二行包含n个各不相同,且都处于区间[-500000, 500000]的整数。
输出:对每组测试数据按从大到小的顺序输出前m大的数。
首先构造测试数据。下面的代码生成60多万个不同的无序的随机数。首先产生100万个随机数,然后用set去重,最后用shuffle打乱即可。
#设本代码的文件名是aa.py
import random
a= []
b= []
for i in range(0,1000000,1): #100万个随机数
a.append(random.randint(-500000,500000))
b=list(set(a)) #去重
#print("lena=",len(a)) #验证a的个数是不是100万个
#print("lenb=",len(b)) #b的个数有60多万个
random.shuffle(b) #打乱b
print(len(b),random.randint(1,len(b))) #打印n、m
for i in range(0,len(b),1): #逐个打印
print ( b[i],end=' ')
#下面的做法是存到一个文件里面,其实不需要
'''
f = open("d:\data.in", "w") #输出到文件里
print(len(b),random.randint(1,len(b)),file=f)
for i in range(0,len(b),1): #逐个打印
print ( b[i],end=' ',file=f)
f.close()
'''
2、对拍
下面以Windows环境为例说明构造测试数据和对拍的过程,linux环境类似。
把上面的代码存为文件aa.py,执行下面的命令,输出测试数据到文件data.in
D:\>C:\Users\hp\AppData\Local\Programs\Python\Python39\python aa.py >data.in
作者的python安装在目录“C:\Users\hp\AppData\Local\Programs\Python\Python39\”,读者可以按自己的目录操作,或者设置环境变量,就不用输这个目录了。
下面给出Hdu 1425的对拍代码,功能是:先输入n和m,然后输入n个数,排序后,打印出前m大的数。与C++对拍代码相比,Python代码更简单。
#设本代码的文件名是bb.py
n,m = map(int,input().split()) #输入n、m
a=[int(n) for n in input().split()] #输入n个数
a.sort() #排序
for i in range(n-1,n-m,-1): #打印出前m-1大的数
print ( a[i],end=' ')
print ( a[n-m]) #打印出第m大的数
把代码存为文件bb.py。Windows环境下,执行以下命令,读输入数据data.in,输出数据到py.out
D:\>C:\Users\hp\AppData\Local\Programs\Python\Python39\python bb.py py.out
前一节“测试数据的构造与对拍https://blog.csdn.net/weixin_43914593/article/details/106863166”的c++代码是:
#include
using namespace std;
const int MAXN = 1000001;
int a[MAXN];
int main(){
int n,m;
while(~scanf("%d%d", &n, &m)){
memset(a, 0, sizeof(a));
for(int i=0; i<n; i++){
int t;
scanf("%d", &t); //此题数据多,如果用很慢的cin输入,肯定TLE
a[500000+t]=1; //数字t,登记在500000+t这个位置
}
for(int i=MAXN; m>0; i--)
if(a[i]){
if(m>1) printf("%d ", i-500000);
else printf("%d\n", i-500000);
m--;
}
}
return 0;
}
设代码的文件名是hash,执行代码,读取输入data.in,输出到文件hash.out
D:\>hash hash.out
下面比较2个代码的输出是否一样,执行以下命令:
D:\>fc py.out hash.out /n
若文件一样,输出(上面一行中的hash.out,在下面显示HASH.OUT,这不是笔误):
正在比较文件 py.out 和 HASH.OUT
FC: 找不到差异
3、把所有过程写成批处理文件2
(1)windows环境
把上述过程写成windows环境的bat批处理。它是一个死循环,在每个循环生成数据并对拍,直到发现错误,出错误的那组输入和输出都留在文件中,可以查看。下面代码中的路径是作者电脑的路径,请读者改为自己电脑的路径。
@echo off
set path=C:\MinGW\bin
g++ -o hash.exe hash.cpp
:loop
set path=C:\Users\hp\AppData\Local\Programs\Python\Python39
python aa.py >data.in
hash.exe hash.out
python bb.py py.out
set path=C:\Windows\System32
fc py.out hash.out
if errorlevel == 1 pause
goto loop
(2)linux环境
linux的文件比较命令是diff。
[root]#diff -c hash.out sort.out
本文没有在linux环境中试Python,这里给出一个c++的参考,功能类似。
#!bin/bash
while true; do
gcc hash.cpp -o hash.exe
gcc sort.cpp -o sort.exe
gcc makedata.cpp -o makedata.exe
./makedata.exe >data.in
./hash.exe hash.out
./sort.exe sort.out
if diff hash.out sort.out; then
echo OK
else
echo wrong
break
fi
done
参考https://www.cnblogs.com/zqifa/p/python-random-1.html ↩︎
感谢华东理工大学17级队员李震 ↩︎