算法竞赛进阶指南学习笔记
0x00 基本算法
0x01位运算
基本概念:
-
补码
- 补码:~x
- 以最高位为0表示正数,最高位为1表示负数
- 对有符号整数int按位取反后~x 表示为
-1 - S
- 反码:-x,直接把每一位取反表示 -C
- 补码与反码在负数表示中绝对值相差1
快速幂:AcWing 89. a^b
求 a 的 b 次方对 p 取模的值。
输入格式
三个整数 a,b,p ,在同一行用空格隔开。
输出格式
输出一个整数,表示a^b mod p的值。
数据范围
0
≤
a
,
b
≤
1
0
9
0≤a,b≤10^9
0≤a,b≤109
1
≤
p
≤
1
0
9
1≤p≤10^9
1≤p≤109
输入样例:
3 2 7
输出样例:
2
if b & 1:ans = ans * a % p
:表示当前位如果为1的话,则
a
c
k
−
i
∗
2
k
−
i
a^{c_{k-i}*2^{k-i}}
ack−i∗2k−i的值就大于1,需要计算当前位对p取模的结果
def main():
a,b,p = map(int,input().split())
ans = 1 % p # 如果b为0,p为1,此时计算结果应该为0,所以这里需要对p取模
while b:
if b & 1:ans = ans * a % p
a = a * a % p # 更新a的值
b >>= 1 # 将b的值右移一位
print(ans)
main()
求 a 乘 b 对 p 取模的值。
输入格式
第一行输入整数a,第二行输入整数b,第三行输入整数p。
输出格式
输出一个整数,表示a*b mod p的值。
数据范围
1≤a,b,p≤1018
输入样例:
3
4
5
输出样例:
2
- 快速幂思想
def main():
a = int(input())
b = int(input())
p = int(input())
ans = 0
while b:
if b & 1:ans = (ans + a) % p # a就是每个乘积项,将每一项对p取模后累加到ans中
a = a * 2 % p
b >>= 1
print(ans)
main()
- 数学思想
def main():
a = int(input())
b = int(input())
p = int(input())
ans = 0
a = a % p
b = b % p
ans = a * b - (a * b // p) * p
print(ans)
main()
- 二进制状态压缩
给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 n。
接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。
对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤n≤20
0≤a[i,j]≤107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
class Solution:
def __init__(self):
N = 21
M = 1 << 21
self.f = [[float("inf")] * N for _ in range(M)]
self.arr = []
def main(self):
n = int(input())
for i in range(n):
line = list(map(int,input().split()))
self.arr.append(line)
self.f[0][0] = 0
for i in range(1 << n):
for j in range(n):
if i >> j & 1:
for k in range(n):
if i >> k & 1:
self.f[i][j] = min(self.f[i][j],self.f[i-(1<<j)][k] + self.arr[k][j])
print(self.f[(1 << n) - 1][n-1])
solution = Solution()
solution.main()
AcWing 998. 起床困难综合症
21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。
作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。
通过研究相关文献,他找到了该病的发病原因: 在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。
正是由于 drd 的活动,起床困难综合症愈演愈烈, 以惊人的速度在世界上传播。
为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。
历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。
drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。
具体说来,drd 的防御战线由 n 扇防御门组成。
每扇防御门包括一个运算 op 和一个参数 t,其中运算一定是 OR,XOR,AND 中的一种,参数则一定为非负整数。
如果还未通过防御门时攻击力为 x,则其通过这扇防御门后攻击力将变为 x op t。
最终 drd 受到的伤害为对方初始攻击力 x 依次经过所有 n 扇防御门后转变得到的攻击力。
由于 atm 水平有限,他的初始攻击力只能为 0 到 m 之间的一个整数(即他的初始攻击力只能在 0,1,…,m 中任选,但在通过防御门之后的攻击力不受 m 的限制)。
为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd 受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。
输入格式
第 1 行包含 2 个整数,依次为 n,m,表示 drd 有 n 扇防御门,atm 的初始攻击力为 0 到 m 之间的整数。
接下来 n 行,依次表示每一扇防御门。每行包括一个字符串 op 和一个非负整数 t,两者由一个空格隔开,且 op 在前,t 在后,op 表示该防御门所对应的操作,t 表示对应的参数。
输出格式
输出一个整数,表示 atm 的一次攻击最多使 drd 受到多少伤害。
数据范围
输入样例:
3 10
AND 5
OR 6
XOR 7
输出样例:
1
样例解释
atm可以选择的初始攻击力为 0,1,…,10。
假设初始攻击力为 4,最终攻击力经过了如下计算
4 AND 5 = 4
4 OR 6 = 6
6 XOR 7 = 1
类似的,我们可以计算出初始攻击力为 1,3,5,7,9 时最终攻击力为 0,初始攻击力为 0,2,4,6,8,10 时最终攻击力为 1,因此 atm 的一次攻击最多使 drd 受到的伤害值为 1。
运算解释
在本题中,选手需要先将数字变换为二进制后再进行计算。如果操作的两个数二进制长度不同,则在前补 0 至相同长度。
OR
为按位或运算,处理两个长度相同的二进制数,两个相应的二进制位中只要有一个为 1,则该位的结果值为 1,否则为 0。
XOR
为按位异或运算,对等长二进制模式或二进制数的每一位执行逻辑异或操作。如果两个相应的二进制位不同(相异),则该位的结果值为 1,否则该位为 0。
AND
为按位与运算,处理两个长度相同的二进制数,两个相应的二进制位都为 1,该位的结果值才为 1,否则为 0。
例如:
我们将十进制数 5 与十进制数 3 分别进行 OR、XOR 与 AND 运算,可以得到如下结果:
0101 (十进制 5) 0101 (十进制 5) 0101 (十进制 5)
OR 0011 (十进制 3) XOR 0011 (十进制 3) AND 0011 (十进制 3)
= 0111 (十进制 7) = 0110 (十进制 6) = 0001 (十进制 1)
class Solution:
def __init__(self):
self.op = list()
def calc(self,bit,now,n):
"""
对第bit位进行n次运算,计算运算结果
:param bit:
:param now:
:param n:
:return:
"""
for i in range(n):
op= self.op[i][0]
x = (int(self.op[i][1]) >> bit) & 1
if op == "AND":now &= x
elif op == "OR":now |=x
else: now ^= x
return now
def main(self):
n,m = map(int,input().split())
for i in range(n):
line = input().split()
self.op.append(line)
val = ans = 0
for bit in range(4,-1,-1):
res0 = self.calc(bit,0,n)
res1 = self.calc(bit,1,n)
if val + (1 << bit) <= m and res0 < res1:
val += (1 << bit)
ans += (res1 << bit)
else:
ans += (res0 << bit)
print(ans)
solution = Solution()
solution.main()
成对变换
lowbit统计非负整数在二进制表示下1的个数
def lowbit(x):
"""
获取x的二进制中最低位1
:param x:
:return:
"""
return x & -x
def main():
n = int(input())
nums = list(map(int,input().split()))
for i in range(n):
res = 0
k = nums[i]
while k:
k -= lowbit(k)
res += 1
print(res,end=' ')
0x02 递推与递归
- 递推和递归的宏观描述:
对于递归算法,我们让程序在每个变化步骤中执行以下三个操作:
- 缩小问题状态空间的规模,这意味着程序在寻找“原问题”与“问题边界”之间的变换路线,并向正在探索的路线迈出一步
- 尝试求解规模缩小以后的问题,结果可能是成功也可能是失败
- 如果成功,则找到了规模缩小后的问题的答案,那么将答案扩展到当前问题,如果失败则重新回到当前问题,程序可能会继续寻找当前问题的其他变换路线,直至最终确定当前位无解
- 如何尝试求解规模缩小后的问题:规模缩小后的问题是原问题的一个子问题,所以我们把它视为一个新的“原问题”,由相同的程序进行求解
【自身调用自身】
- 如果求解子问题失败,程序需要重新回到当前问题去寻找其他的变换路线,因此把当前问题缩小为子问题时所做的对当前问题状态产生影响的事情应该全部失效
【回溯时还原现场】
-