# 线性筛 defsieve(n): # find out all of the prime numbers between 2 and n prime = [] is_prime = [True]*(n+1) for i inrange(2,n+1): if is_prime[i]: prime.append(i) for j in prime: # 注意这三个操作的顺序不可调换 if i*j > n: break is_prime[i*j] = False if i%j == 0: # 只用最小的质因数筛除非质数,减少了重复的运算 # 因为j已经筛过一遍了,所以不需要用i重复筛了 break return prime
只需要对线性筛算法中筛除非质数的部分进行修改,在筛除某个值的同时记录筛除它的数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defsieve(n): prime = [] is_prime = [True]*(n+1) min_prime = [1]*(n+1) for i inrange(2,n+1): if is_prime[i]: prime.append(i) # 素数的最小质因子是它本身 min_prime[i] = i for j in prime: if i*j > n: break is_prime[i*j] = False # 在排除数字i*j的同时记录它的最小质因子j # 注意此处i不一定是质数 min_prime[i*j] = j if i%j == 0: break return min_prime
# 利用性质一计算欧拉函数 defeuler(n): res = n for i inrange(2,int(n**0.5)+1): if n%i == 0: while n%i == 0: n //= i res = res//i*(i-1) if n > 1: res = res // n * (n-1) return res
# BF做法:直接对每一个在区间[a,b]内的数从2开始试除,但由于时间复杂度过高,一定会超时 # firstly,find out all the prime number that are smaller than n+1 defsieve(n): prime = [] is_prime = [True]*(n+1) for i inrange(2,n+1): if is_prime[i]: prime.append(i) for j in prime: if i*j > n: break is_prime[i*j] = False if i%j == 0: break return prime # main part a,b = map(int,input().split()) prime = sieve(b) # divide every number in the interval for i inrange(a, b+1): print(f'{i} = ', end = '') # create a string to store the result of division line = [] for j in prime: if i == 1: break while i%j == 0: line.append(str(j)) line.append('*') i //= j # print the result of division line.pop(-1) print(''.join(line))
例题 2:互质数的个数
题目描述:
给定 a,b,求 1≤x<ab中有多少个 x 与 ab。由于答案可能很大,你只需要输出答案对 998244353 取模的结果。
# 带入欧拉公式推论一 # 快速幂 + 欧拉公式 MOD = 998244353 defqpow(a,n): ans = 1 while n: if n&1: # n is odd ans = ans * a % MOD a = a * a % MOD n >>= 1 return ans defeuler(n): res = n for i inrange(2,int(n**0.5)+1): if n%i == 0: while n%i == 0: n //= i res = res//i*(i-1) if n > 1: res = res//n*(n-1) return res
a,b = map(int,input().split()) if a == 1: print(0) else: print(euler(a) * qpow(a,b-1) % MOD)
from collections import defaultdict # calculate out the prime numbers that smaller than the given number defsieve(n): prime = [] is_prime = [True]*(n+1) for i inrange(2, n+1): if is_prime[i]: prime.append(i) for j in prime: if i*j > n: break is_prime[i*j] = False if i%j == 0: break return prime n = int(input()) prime = sieve(n) # divide the given number by these prime numbers # use a defaultdict to record the times that every number appear in the given number d = defaultdict(int) for i in prime: while n%i == 0: d[i] += 1 n //= i # use the data to figure out the answer ans = 1 for i in d.values(): ans *= (i+1) print(ans)
# BF algorithm n = int(input()) res = [] for i inrange(1, n+1): if i > (n//i): # all of the numbers that bigger than this value have alreadly been recorded break if n%i == 0: res.append(i) n //= i # record the number that left as well if n != i: res.append(n) res.sort() print(res)
defqpow(a,n,mod): if n == 0: return1 res = 1 while n: if n&1: # n is an odd number res = res * a % mod n >>= 1# n need to be divided by 2 a = a * a % mod return res
# 暴力做法,只能通过40%的样例 # 0只能通过乘以10的倍数或5的倍数获得,因为2的倍数远多于5的倍数,所以不用考虑 # 100,1200之类的数需要特别处理 from sys import exit
k = int(input()) if k < 0: print(-1) exit() # 从5开始,以5为步长搜索5的倍数和10的倍数 n = 0# 记录当前遍历到的因数 cur = 0# 记录当前所得到的0的个数 while cur < k: n += 5 # 先将n末尾所有的0转移到cur上 temp = n while temp%10 == 0: temp //= 10 cur += 1 # 然后统计temp中5的个数 while temp%5 == 0: temp //= 5 cur += 1 if cur == k: print(n) else: print(-1)
注意到对于一个给定的 n!,末尾0的个数 = 从1到n各个数字的约数中5的个数 = k k=51n+52n+53n+... 可以高效地求出指定n!的末尾0个数,所以可以使用二分法提高查找效率。
# Binary Search Optimize # 注意到末尾0的个数随n的增大只会增大,不会减小,且结果的末尾0个数有大于等于k和小于k两种状态,所以可以使用二分法高效求解 defcheck(n): # 求出 n! 末尾0的个数 res = 0 # 这个过程的实质是求出构成阶乘的数字中所有5的倍数,25的倍数,125的倍数... while n: n //= 5 res += n return res
# binary search main part k = int(input()) left = 1 right = 10**19 while left < right: mid = (left+right)//2 if check(mid) < k: left = mid+1 else: # mid 可能是正确答案,所以不能舍弃 right = mid # left = right if check(left) == k: # 存在解 print(left) else: print(-1)
例题 2:阶乘的和(蓝桥杯第14届省赛真题)
题目描述:
给定 n 个数 Ai,问能满足 m! 为 ∑i=1n(Ai!) 的因数的最大的 m 是多少。其中 m! 表示 m 的阶乘,即 1×2×3×⋅⋅⋅×m。
输入格式:
输入的第一行包含一个整数 n 。
第二行包含 n 个整数,分别表示 Ai,相邻整数之间使用一个空格分隔。
n = int(input()) a = sorted([int(i) for i ininput().split()]) # 从最小的a[0]开始递推 # 创建一个变量记录当前阶乘的系数 k = 1 # 创建一个变量记录当前阶乘的值 cur = a[0] for i inrange(1,n): if cur == a[i]: k += 1 else: # cur < a[i] # 检查系数是否能够合并进当前阶乘中 while k%(cur+1) == 0and k//(cur+1) != 0: cur += 1 k //= cur if cur == a[i] : break # 无法达到a[i] if cur < a[i]: break else: # 成功到达a[i] k += 1 print(cur)
# 逆元的求解 —— 拓展欧几里得 defexgcd(a, b): if b == 0: return a,1,0 x1,y1,gcd = exgcd(b, a%b) x = y1 y = x1 - (a//b)*y1 return gcd,x,y
# 逆元求解主函数 definv(a, p): gcd, x, y = exgcd(a, p) if gcd != 1: raise ValueError(f"The modular inverse does not exist for {a} mod {p}") # 返回 x%p 的结果 return (x % p + p) % p
六、异或 & 异或和
异或操作是一种常用的二进制位运算,当两个操作数相同位上的数值不同时结果为1,数值相同的时候为0\
(1)基本性质
交换律:(a ^ b) = (b ^ a)
结合律:(a ^ b) ^ c = a ^ (b ^ c)
对于任何数x,都有x ^ x=0,x ^ 0 = x
自反性:a ^ b ^ b = a, a ^ 0 = a
例题 1:异或和(蓝桥杯第14届省赛)
问题描述:
给一棵含有 n 个结点的有根树,根结点为 1 ,编号为 i 的点有点权 ai(i∈[1,n])。现在有两种操作,格式如下: 1xy :该操作表示将点 x 的点权改为 y。 2x :该操作表示查询以结点 x 为根的子树内的所有点的点权的异或和。
现有长度为 m 的操作序列,请对于每个第二类操作给出正确的结果。
输入格式:
输入的第一行包含两个正整数 n,m 用一个空格分隔。第二行包含 n 个整数 $a_1, a_2, … ,a_n
,相邻整数之间使用一个空格分隔。接下来 n−1 行,每行包含两个正整数 ui,vi,表示结点 ui 和 vi之间有一条边。接下来 m 行,每行包含一个操作。
# 求 DFS 序,以便建立树状数组 cnt = 0 defdfs(cur,pre): # cur 是当前节点的序号,pre是上一个节点的序号 global cnt cnt += 1 # 记录将当前节点压入栈中的时间戳 seq[cur][0] = cnt for i in tree[cur]: if pre != i: dfs(i,cur) # 记录将当前元素出栈的时间戳,自此以后的时间戳均与以cur为根节点的树无关 seq[cur][1] = cnt
# 树状数组函数三件套 deflowbit(x): return x&(-x)
defmodify(x,v): global n while x <= n: t[x] ^= v # 计算异或和 x += lowbit(x)
defquery(x): global n ans = 0 while x > 0: ans ^= t[x] x -= lowbit(x) return ans
# 接收输入,创建数据结构 n,m = map(int,input().split()) # a[]存储每一个点的权值 a = [0] + [int(i) for i ininput().split()]
# 用邻接表存储树结构 tree = [[] for i inrange(n+1)] for _ inrange(n-1): u,v = map(int,input().split()) # 注意没说方向,是一个无向边 tree[u].append(v) tree[v].append(u)
# 创建一个二维数组seq[][]记录DFS序 # 其中seq[i]是有2个元素的列表,两个元素分别是第i个节点入栈和出栈的时间戳 seq = [[0,0] for i inrange(n+1)] dfs(1,0)
# 为DFS序数组创建树状数组,并用a[]的值初始化 t = [0]*(n+1) for i inrange(1,n+1): modify(seq[i][0], a[i]) for _ inrange(m): instr = [int(i) for i ininput().split()] if instr[0] == 1: # 修改元素,注意到需要在赋值的同时清除原有元素,所以将原值与新值异或,相当于清除原值 modify(seq[instr[1]][0], a[instr[1]]^instr[2]) # 维护a[],确保其始终保存着每一个节点的当前值 a[instr[1]] = instr[2] else: # 输出单点查询结果 print(query(seq[instr[1]][1]) ^ query(seq[instr[1]][0] - 1))