10天掌握基础算法

day2

1.队列

from queue import Queue 
q = Queue(maxsize=0)#maxsize设置队列中数据上限,小于或等于0则不限制;容器中大于这个数则阻塞,直到队列中的数据被消掉 

Queue.qsize() 返回队列的大致大小。

Queue.empty() 如果队列为空,返回 True 否则返回 False

Queue.full() 如果队列是满的返回 True ,否则返回 False

Queue.put(item, block=True, timeout=None)

  • 常用时忽略默认参数,即使用 Queue.put(item)

Queue.get(block=True, timeout=None)

  • 常用时忽略默认参数,即使用 Queue.get()

Python 中的队列线程安全,太慢了,所以我们一般用deque (双端队列)。它是Python内置的一个高效的序列数据结构,属于collections模块。它支持从两端快速添加(append)和删除(pop)元素,比如你可以用它来实现栈(stack)和队列(queue)。deque是双端队列的缩写,代表它可以从头部或尾部添加或删除元素,而操作的时间复杂度都是O(1)。

from collections import deque
d = deque()#创建一个deque对象
d = deque([1, 2, 3, 4])#也用一个可迭代对象初始化deque
d.append(5)  # d 现在是 deque([1, 2, 3, 4, 5])
d.appendleft(0)  # d 现在是 deque([0, 1, 2, 3, 4, 5])
d.pop()  # 返回 5, d 现在是 deque([0, 1, 2, 3, 4])
d.popleft()  # 返回 0, d 现在是 deque([1, 2, 3, 4])

 2.字典

#创建字典
massege={'小李':'123124543643','xiaohua':'17855666','LiMing':'1249699859456'}

#或者创建空的字典
empty_dict = {}
#或者使用元组作为key
group_dict = {(60, 99):'good', 100:'nice'}


# 如果字典内不含有相应的Key值,则会执行添加操作
dict[key]=value

# 如果字典内含有相应的Key值,则会执行更新操作
dict[key]=new_value

# 使用update()修改
# update() 方法可使用一个字典所包含的 key-value 对来更新己有的字典。如果有就修改,没有就添加。
dict.update({'key':123,'key2':234})

del dict['key']  # 删除键是'key'的条目
dict.clear()      # 清空字典所有条目
del dict          # 删除字典

dict = {'Name': 'Zara', 'Age': '7'}
print (dict['Name'])#当然如果key值不存在,将会抛出异常
#也可以是用get()方法,不存在会返回None,但不会抛出异常
print(dict.get('Name'))

3.集合 

# 1. set 的定义 变量名=set()
intSet=set()
stringSet=set()

s1 = "测试1"
s2 = "测试2"
s3 = "测试3"
# 2. 插入操作

stringSet.add(s3)
stringSet.add(s2)

#5.返回集合元素数量
print("前2次插入操作完成后的元素数量为%d\n",len(stringSet))
stringSet.add(s1)
print("前3次插入操作完成后的元素数量为%d\n",len(stringSet))


#6.遍历整个集合
for ele in stringSet:
    print(ele,end=' ')
print()

#3. 删除操作
stringSet.discard(s3)

print("删除操作完成后的元素数量为%d\n",len(stringSet))
for ele in stringSet:
    print(ele,end=' ')
print()

# 4. 判断是否由此元素
if  s2 in stringSet:
    print("存在元素")
前2次插入操作完成后的元素数量为%d
 2
前3次插入操作完成后的元素数量为%d
 3
测试1 测试2 测试3 
删除操作完成后的元素数量为%d
 2
测试1 测试2 
存在元素

day3

1.简单枚举

ans = [[] for i in range(10)]
a = ['0']*10
if __name__ == '__main__':
c = input().split()
for i in range(6):
    if c[i] == 'A':
        a[i] = 1
    elif c[i] == 'J':
        a[i] = 11
    elif c[i] == 'Q':
        a[i] = 12
    elif c[i] == 'K':
        a[i] = 13
    else:
        a[i] = ord(c[i]) - ord('0')

ans[0].append(a[0])
for i in range(1, 6):
    for j in range(len(ans[i - 1])):
        ans[i].append(ans[i - 1][j] + a[i])
        ans[i].append(ans[i - 1][j] - a[i])
        ans[i].append(ans[i - 1][j] * a[i])
        ans[i].append(int(ans[i - 1][j] / a[i]))
flag = 0
for j in range(len(ans[5])):
    if ans[5][j] == 42:
        flag = 1
        break
if flag == 1:
    print("YES")
else:
    print("NO")

2.组合问题

chosen=[]
m=0
n=0
s=''
name=[]
ans=[]
def calc(x):#组合问题(n个数选m个)函数表示选择到第x个数了
  ansTem=''
  if len(chosen)>m or (len(chosen)+(n-x+1))<m:#当选择的数量大于m(表示选够了),或者选择的数量+剩余的数量n-x+1<m表示不够选了。则可以剪枝
    return
  elif x==n+1:#当选到n+1个时候,表示n个数已经选完了,打印结果
    # for i in range(len(chosen)):
    #   ansTem+=name[chosen[i]-1]+' '
    # ans.append(ansTem)
    # return
    for i in chosen:
      ansTem = ansTem + name[i - 1] + ' '#数是从1开始选的,而数组下表从0开始,形成映射
      # print(ansTem)
    ans.append(ansTem)
    return
  #每个数都有两种情况,即选或者不选
  chosen.append(x)#选第x个数
  calc(x+1)#处理下一个数
  chosen.pop()#不选选第x个数
  calc(x+1)
n,m=map(int,input().split())
for i in range(n):
  s=input()
  name.append(s)
calc(1)
for i in range(len(ans)):
  print(ans[i])

手写子集(二进制法)

a=[1,2,3,4,5,6,7,8,9,10,11,12,13,14]
def print_subset(n):#求子集
    for i in range(1 << n):#即0-2**n-1共2**n个子集
        for j in range(n):#遍历1、10、100
            if (i & (1 << j) !=0):#按位与为1则找到子集元素
                print(a[j],end=" ")
        print()#一个子集结束
print(print_subset(3))#求n=3的集合{1,2,3}的子集

3.排列问题

手写全排列

chosen=[0]*20#判断选过与否
order=[0]*20#存储已经排好序的
s=''
name=[]
ans=[]
def calc(k):#处理排在k位置数
    ansTem=""
    if k==n+1:#位置来到k+1则处理完毕一组,打印
        for i in range(1,n+1):
            
            ansTem+=name[order[i]-1]+" "
        print(ansTem)
        return
    for i in range(1,n+1):
        if chosen[i]==1:#如果该数选过了,就选下一个
            continue
        order[k]=i#没选过就选上
        chosen[i]=1#标记选过
        calc(k+1)#下一个位置
        order[k]=0#不选该数了
        chosen[i]=0#取消标记
n=int(input())
for i in range(n):
    s=input()
    name.append(s)
calc(1)

在python中有一个强大的函数permutation,它可以完成全排列。

功能:连续返回由iterable序列中的元素生成的长度为r的排列

如果未指定r或者r为None,则r为iterable的长度,即生成包含所有元素的全排列 

例如:

上图中左侧为输出 

#输入一串字符串,求出该字符串在该字符串全排列之后的序号。(初始序号为0)
from itertools import*
cnt=0
string=input()
a=list(string)
a.sort()
for i in permutations(a):
  a=''.join(i)
  if string==a:
    print(cnt)
    break
  else:
    cnt+=1

给出N个数的排列,输出这个排列后面的第M个排列

输入样例
5
3
1 2 3 4 5
输出样例
1 2 4 5 3

#permutations()函数是按元素位置进行排列输出的,即排序成最小列,然后从最小列开始permutations(),遇到给定的排列再往后数m个排列,输出。这会超时。
#手写下一个排列:从当前排列开始暴力的寻找下一个排列。对于当前排列,从后往前比较,寻找nums[i-1]<nums[i]的位置,把nums[i-1]与i到末尾中比nums[i-1]大的最小数交换,再将i-1之后的数进行翻转,可以得到比当前排列大的最小排列。
n=int(input())
m=int(input())
nums=list(map(int,input().split()))
def find(nums):
  for i in range(n-1,0,-1):
    if nums[i-1]<nums[i]:
      for j in range(n-1,i-1,-1):
        if nums[j]>nums[i-1]:
          nums[j],nums[i-1]=nums[i-1],nums[j]
          return nums[:i] + nums[:i-1:-1]
for i in range(m):
  nums=find(nums)
print(" ".join([str(i) for i in nums]))

数组的处理

4.尺取法

回文数判断

s=list(input())
i=0
j=len(s)-1
if i==j:
    print("YES")
else:
    while s[i]==s[j]:
        i+=1
        j-=1
        if j<=i:
            print("YES")
            break
    else:
        print("NO")

输入n个数,放在数组a[]中。找出其中两个数,他们之和为m

n,m=map(int,input().split())
s=[]
for i in range(n):
    a=input()
    s.append(int(a))
s.sort()
i=0
j=len(s)-1
if i==j:
    print("NO")
else:
    while s[i]+s[j]<m:
        i+=1#小的变大
        if j<=i:
            print("NO")
            break
    while s[i]+s[j]>m:
        j-=1#大的变小
        if j<=i:
            print("NO")
            break
    else:
        print(s[i],s[j])                         

美丽区间

n,s=map(int,input().split())
sum=0
ans=10**8
a=list(map(int,input().split()))
i=0#右指针
j=0#左指针
while i < n:#遍历整个列表
    if sum<s:
        sum+=a[i]
        i+=1#和不够,则右指针右移
    else:
        ans=min(i-j,ans)#记录最小长度
        sum-=a[j]
        j+=1#和过多,则左指针右移
if ans==10**8:
    print('0')
else:
    print(ans)

 day4

1.递推与递归

#斐波那契数列
#1.递推
n=int(input())
x,y=0,1
ans=0
if n==0:
    ans=0
elif n==1:
    ans=1
else:
    for i in range(n-1):
        ans=x+y
        x=y
        y=ans
print(ans)

#2.递归
n=int(input())
def f(n):
    if n==0:
        return 0
    elif n==1:
        return 1
    else:
        return f(n-2)+f(n-1)
print(f(n))

2.存储型的递推与递归

#1.递推
m=int(input())
F=[0]*35
F[0]=0
F[1]=1
for i in range(2,31):
    F[i]=F[i-1]+F[i-2]
for i in range(m):
    n=int(input())
    print(F[n])

#2.递归
m=int(input())
F=[0]*35
def f(n):
    if n==0:
        F[0]=0
        return 0
    elif n==1:
        F[1]=1
        return 1
    else:
        F[n]=f(n-1)+f(n-2)
        return F[n]
f(30)
for i in range(m):
    n=int(input())
    print(F[n])

 数字三角形

n=int(input())
a=[[0]*101]*101
for i in range(1,n+1):
    a[i]=input().split()
    a[i]=list(map(int,a[i]))
for i in range(n-1,0,-1):
    for j in range(0,i):
        if a[i+1][j]>=a[i+1][j+1]:
            a[i][j]+=a[i+1][j]
        else:
            a[i][j]+=a[i+1][j+1]
print(max(a[1]))

数字三角形(向左下走的次数与向右下走的次数差不大于1)

n=int(input())
a=[[0]*101]*101
for i in range(1,n+1):
    a[i]=input().split()
    a[i]=list(map(int,a[i]))
for i in range(2,n+1):
    for j in range(0,i):
        if j==0:
            a[i][j]+=a[i-1][j]
        elif j==i-1:
            a[i][j]+=a[i-1][j-1]
        else:
            a[i][j]+=max(a[i-1][j-1],a[i-1][j])
if n%2==0:    
    print(max(a[n][(n//2)-1],a[n][(n//2)]))
else:
    print(a[n][(n//2)])

数的划分

n,k=map(int,input().split())
#dp=[[0]*210]*211,这种写法不对,改一行,其他行也会改变
dp=[[0]*210 for i in range(211)]#dp[i][j]表示整数i分为j份的分法数
for i in range(1,n+1):
    dp[i][1]=1#整数i分为1份,显然只有一种分法
    dp[i][i]=1#整数i分为i份,显然也只有一种分法
for i in range(3,n+1):#dp[1][1]\dp[2][1]\dp[2][2]\dp[3][1]都已经初始化,从dp[3][2]开始
    for j in range(2,k+1):
        if i>j:#整数i显然必须大于分数,否则不合理(如3如何分为4份?)
            dp[i][j]=dp[i-j][j]+dp[i-1][j-1]#每种情况分为两种情况(1.先拿一个给一份,剩下的分为k-1份。2.先拿k个分别拿一个分给k份,剩下的i-k再分为k份)两种情况和为总情况
print(dp[n][k])

数的计算

def f(n):#递归的枚举每种情况
    global res
    if n==1:#n为1,只有一个数即1
        return
    for i in range(1,n//2+1):#枚举左边的所有情况
        res+=1#满足条件加1
        f(i)#每个情况自己左边也可以枚举
    
res=1
n=int(input())
f(n)
print(res)

 day5

1.DFS

N皇后问题

x=[0]*15#存放第i个放置皇后的列号
sum=0#有几种放法
n=0
def pd(k):#判断前k个皇后与第K个皇后的位置是否冲突
    for i in range(1,k):
        if abs(x[i]-x[k])==k-i:#列差与行差相等,那么斜率为正负1,出现在斜线上,不符合题意
            return 0
        elif x[i]==x[k]:#列号不能相同
            return 0
    return 1
def check(a):
    global sum
    if a>n:#放到第n+1个,显然前n个放完了,方法数+1
        sum+=1
    else:
        return 0
    return 1
def DFS(a):
    if check(a):#已经放了n个皇后了,回溯,找下一种方法
        return
    else:
        for i in range(1,n+1):#遍历每一列
            x[a]=i
            if pd(a):#能发就找下一行
                DFS(a+1)
            else:#不能就找下一列
                continue
n=int(input())
DFS(1)
print(sum)

路径之谜

flag=[[0 for i in range(25)] for j in range(25)]#标记数组,判断坐标(i,j)是否走过
flag[1][1]=1
resx=[0 for i in range(1000)]#存储路径
resy=[0 for i in range(1000)]
resx[0]=1#存储起点
resy[0]=1
resCount=1#该走第几步了
xyid=0#坐标点的序号
n=int(input())
y=input().split()#先读入y轴箭靶上的箭数(北面箭靶)
x=input().split()
xj=list(map(int,x))
yj=list(map(int,y))
yj=[0]+yj
yj[1]-=1
xj=[0]+xj
xj[1]-=1
dx=[1,-1,0,0]#走的方向,可以向上、下、左、右四个方向走
dy=[0,0,1,-1]
def pd(x,y):#判定函数,判断这条路是否走过,是否出界了,是否还有剩余的箭
    if flag[x][y]==1 or x<1 or x>n or y<1 or y>n or xj[x]<=0 or yj[y]<=0:
        return 0
    else:
        return 1
def check(x,y):#检查函数,判断是否走到终点了
    if x==n and y==n:
        if (sum(xj)+sum(yj))!=0:#箭靶上还有箭,表示路径走的不对
            return 0
        else:#箭靶上没有箭,打印路径
            for i in range(resCount):
                xyid=(resx[i]-1)*n+resy[i]-1
                print(xyid,end=' ')
            return 1
    else:
        return 0
def dfs(x,y):
    global resCount
    if check(x,y):#走到终点了,结束
        return
    else:#否则继续走
        for i in range(4):#四个方向
            xt=x+dx[i]
            yt=y+dy[i]
            if pd(xt,yt):#判定函数,看能不能走,能走就走
                resx[resCount]=xt#更新坐标
                resy[resCount]=yt
                flag[xt][yt]=1#标记路径
                resCount+=1#更新步数
                xj[xt]-=1#拔箭
                yj[yt]-=1
                dfs(xt,yt)#走下一步
                resCount-=1#回溯,步数回退
                xj[xt]+=1#把箭插回去
                yj[yt]+=1
                flag[xt][yt]=0#取消标记
            else:#不能走就换方向
                continue
dfs(1,1)

最大数字

n,a,b=map(int,input().split())
s=str(n)
ans=0
def dfs(i,sum_,a,b):#搜到第i个数时,此时累计的和(sum_*10就是此位数之前的位数和)
    global ans,s
    if i==(len(s)):#每一位都操作了,更新ans,取所有方案的最大值
        ans=max(ans,sum_)
        return
    x=int(s[i])#取出当前数位值
    t=min(9-x,a)#要么操作9-x次使之变为9,要么操作a次使之变大
    dfs(i+1,sum_*10+x+t,a-t,b)#操作位数+1,此时累计的和+t,a操作-t
    if (x+1)>b:#b操作减小数值,将一个数变为9需要x+1次,不够就放弃操作,直接找下一位
        dfs(i+1,sum_*10+x,a,b)
    else:
        dfs(i+1,sum_*10+9,a,b-(x+1))#够便操作位数+1,此时累计的和+9,b操作x+1次
dfs(0,0,a,b)
print(ans)

 2.BFS

长草

from collections import deque
q=deque()
dx=[1,-1,0,0]
dy=[0,0,1,-1]
n,m,k=0,0,0
n,m=map(int,input().split())
Map=[[0 for i in range(m)] for j in range(n)]

def pd(x,y):
    if x<0 or x>=n or y<0 or y>=m or Map[x][y]=='g':
        return 0
    else:
        return 1
def bfs():
    global m,n,k,Map,q,length
    while k>0 and len(q)>0:
        temp=q.popleft()
        x=temp[0]
        y=temp[1]
        for i in range(4):
            xt=x+dx[i]
            yt=y+dy[i]
            if not pd(xt,yt):
                continue
            else:
                Map[xt][yt]='g'
                q.append((xt,yt))
        length-=1
        if length==0:
            k-=1
            length=len(q)
for i in range(n):
    input_=input()
    for j in range(m):
        Map[i][j]=input_[j]
        if Map[i][j]=='g':
            q.append((i,j))
length=len(q)
k=int(input())
bfs()
for i in range(n):
    s=''
    for j in range(m):
        s+=Map[i][j]
    print(s)

用数组模拟队列,效率更高 

n,m,k=0,0,0
q=[]
qfont,qend=0,0
dx=[1,-1,0,0]
dy=[0,0,1,-1]
def pd(x,y):
    if x<0 or x>=n or y<0 or y>=m or Map[x][y]=='g':
        return 0
    else:
        return 1
def BFS():
    global k,m,n,Map,length,q,qfont,qend
    while k>0 and length>0:
        temp=q[qfont]
        qfont+=1
        x=temp[0]
        y=temp[1]
        for i in range(4):
            xt=x+dx[i]
            yt=y+dy[i]
            if pd(xt,yt):
                q.append((xt,yt))
                qend+=1
                Map[xt][yt]='g'
            else:
                continue
        length-=1
        if length==0:
            k-=1
            length=qend-qfont
n,m=map(int,input().split())
Map=[[0 for i in range(m)] for j in range(n)]
for i in range(n):
    input_=input()
    for j in range(m):
        Map[i][j]=input_[j]
        if Map[i][j]=='g':
            q.append((i,j))
            qend+=1
length=qend-qfont
k=int(input())
BFS()
for i in range(n):
    s=''
    for j in range(m):
        s+=Map[i][j]
    print(s)

 走迷宫

q=[]
qfont,qend=0,0
n,m,xs,ys,xe,ye=0,0,0,0,0,0
n,m=map(int,input().split())
G=[[0 for i in range(m+10)] for j in range(n+10)]
vis=[[0 for i in range(m+10)] for j in range(n+10)]
dx=[0,0,1,-1]
dy=[1,-1,0,0]
ans=-1
for i in range(n):
    input_=input().split()
    for j in range(m):
        G[i+1][j+1]=input_[j]
xs,ys,xe,ye=map(int,input().split())
vis[xs][ys]=1
q.append((xs,ys))
qend+=1
def pd(x,y):
    if vis[x][y]!=0 or x<1 or x>n or y<1 or y>m or G[x][y]!='1':
        return 0
    else:
        return 1
def check(x,y):
    global ans
    if x==xe and y==ye:
        ans=vis[x][y]-1#题目案例没算起点
        return 1
    else:
        return 0
def bfs():
    global qfont,qend
    while (qend-qfont)!=0:
        temp=q[qfont]
        qfont+=1
        x=temp[0]
        y=temp[1]
        if check(x,y):
            return
        else:
            for i in range(4):
                xt=x+dx[i]
                yt=y+dy[i]
                if pd(xt,yt):
                    vis[xt][yt]=vis[x][y]+1
                    q.append((xt,yt))
                    qend+=1
                else:
                    continue
bfs()
print(ans)

迷宫

from collections import deque
q=deque()
q.append((0,0))
n=30
m=50
Map=[[int(i) for i in input()] for j in range(n)]
vis=[[0 for i in range(m)] for j in range(n)]
go=[['' for i in range(m)] for j in range(n)]
vis[0][0]=1
while q:
    x,y=q.popleft()
    if x==29 and y==49:
        print(go[-1][-1])
        break
    else:
        for i,j,k in [[1,0,'D'],[0,-1,'L'],[0,1,'R'],[-1,0,'U']]:
            a=x+i
            b=y+j
            if 0<=a<n and 0<=b<m and vis[a][b]==0 and Map[a][b]==0:
                vis[a][b]=vis[x][y]+1
                go[a][b]=go[x][y]+k
                q.append((a,b)) 

day6 

1.差分法

 2.前缀和

day7 

1.并查集

普通合并 

def init():
    for i in range(10**6+10):
        fa.append(i)
def find(x):
    if fa[x]==x:
        return x
    else:
        fa[x]=find(fa[x])
        return fa[x]
def merge(x,y):
    fa[find(x)]=find(y)
ans=0
fa=[]
init()
m,n=map(int,input().split())
k=int(input())
for i in range(k):
    a,b=map(int,input().split())
    merge(a,b)
for i in range(1,m*n+1):
    if fa[i]==i:
        ans+=1
print(ans)

 优化合并

def init():
    for i in range(10**6+10):
        fa.append(i)
        rk.append(1)#初始化连通分量的深度为1
def find(x):
    if fa[x]==x:
        return x
    else:
        fa[x]=find(fa[x])
        return fa[x]
def merge(x,y):
    x=find(x)
    y=find(y)
    if x!=y:
        if rk[x]>rk[y]:
            x,y=y,x#保证y的深度始终大于x
        fa[x]=y#将x合并到y上
        if rk[x]==rk[y]:
            rk[y]+=1
ans=0
fa=[]
rk=[]
init()
m,n=map(int,input().split())
k=int(input())
for i in range(k):
    a,b=map(int,input().split())
    merge(a,b)
for i in range(1,m*n+1):
    if fa[i]==i:
        ans+=1
print(ans)

修改数组

暴力法

n=int(input())
a=list(map(int,input().split()))
b=set()
for i in range(n):
  
    while a[i] in b:
        a[i]=a[i]+1
    b.add(a[i])
for i in range(n):
  print(a[i],end=' ')

 并查集

fa=[0 for i in range(10**6+10)]
def go():
    for i in range(10**6+10):
        fa[i]=i
def find(x):
    if fa[x]==x:
        return x
    else:
        fa[x]=find(fa[x])
        return fa[x]
n=int(input())
a=list(map(int,input().split()))
go()
for i in range(n):
    #当a[i]没出现过,显然父亲就是自己,a[i]不变;当a[i]出现过,a[i]用父亲更新保证更新不重复
    x=find(a[i])#将a[i]的父亲赋给x,再用x更新a[i]
    a[i]=x
    fa[a[i]]=find(x+1)#a[i]父亲为x+1,即比自己大1的值,因为大1的值也可能出现过,所以找x+1的父亲
for i in range(n):
    print(a[i],end=" ")

 2.并查集撤销操作

蓝桥地图

fa=[]
rk=[]
undo_fa=[]
undo_rk=[]
class UndoObject:
    def __init__(self,pos,val):
        self.pos=pos
        self.val=val
def go():
    for i in range(10**5+10):
        fa.append(i)
        rk.append(1)
def find(x):
    if fa[x]==x:
        return x
    else:
        return fa[x]
def merge(x,y):
    x=find(x)
    y=find(y)
    if x!=y:
        if rk[x]>rk[y]:
            x,y=y,x
        undo_fa.append(UndoObject(x,fa[x]))
        fa[x]=y
        undo_rk.append(UndoObject(y,rk[y]))
        if rk[x]==rk[y]:
            rk[y]+=1
def undo():
    undo_fa_top=undo_fa.pop()
    fa[undo_fa_top.pos]=undo_fa_top.val
    undo_rk_top=undo_rk.pop()
    rk[undo_rk_top.pos]=undo_rk_top.val
n,m=map(int,input().split())
go()
for i in range(m):
    op,u,v=map(int,input().split())
    if op==1:
        if find(u)!=find(v):
            merge(u,v)
        else:
            undo()
    if op==2:
        if find(u)!=find(v):
            print("N")
        else:
            print("Y")

 注:上题用并查集撤销操作只能过40%弱数据,有如下例子:

#一开始都未联通

1 u1 v1

1 u2 v2

1 u1 v1

以上操作先将u1 v1联通,再将u2 v2联通,注意最后的操作,因为u1 v1已经联通,我们希望将u1 v1断开,可是并查集撤销操作会将最近一次的操作撤销即u2 v2会断开。因此需要用到线段树时间分治的知识来解决,感兴趣的读者可以自行搜索。

day8

1.二分法

模板

#找>=x的最小值
while low<height:
    mid=(low+height)//2
    if a[mid]>=x:
        height=mid
    else:
        low=mid+1
#找<=x的最大值
while low<height:
    mid=(low+height+1)//2
    if a[mid]<=x:
        low=mid
    else:
        height=mid-1

2.整数二分

L,N,M=map(int,input().split())
l=0
r=L
a=[0 for i in range(N)]
for i in range(N):
    a[i]=int(input())
a=[0]+a+[L]
def check(d):#检查当前的最小距离是否能够在移动M次内找到,不能就减小d能就加大d
    num=0
    pos=0
    for i in range(1,N+2):
        if a[i]-pos<d:
            num+=1
        else:
            pos=a[i]
    if num<=M:
        return 1
    else:
        return 0
while l<r:#<=x的最大值模板
    mid=(l+r+1)//2
    if check(mid):
        l=mid
    else:
        r=mid-1
print(r)

分巧克力

n,k=map(int,input().split())
hi=[]
wi=[]

for i in range(n):
        h,w=map(int,input().split())
        hi.append(h)
        wi.append(w)
def check(d):#检查当前边长是否能够分给k个人,能就加大d,不能就减小d
    sum_=0
    for i in range(n):
        sum_+=(hi[i]//d)*(wi[i]//d)
        if sum_>=k:#提前判断第i个巧克力之前切割后的总和够不够分,不用等到所有巧克力切割完再判断
            return 1
    return 0
l=0
r=max(max(hi),max(wi))
while l<r:#<=x的最大值模板
    mid=(l+r+1)//2
    if check(mid):
        l=mid
    else:
        r=mid-1
print(l)

 3.实数二分

模板

eps=1e-6
while (l+eps)<r:
    mid=(l+r)/2
    if pd():
        r=mid#实数二分无需考虑边界问题
    else:
        l=mid

M次方根

n,m=map(int,input().split())
def pd(a,m):#求a的m次幂,如果大于n,显然大于a的所有值的m次幂必然大于n,因此考虑(l,mid)否则考虑(mid,r)
    cnt=m
    total=1
    while cnt>0:
        total*=a
        cnt-=1
    if total>=n:
        return 1
    else:
        return 0
eps=1e-8
l=0
r=n
while (l+eps)<r:
    mid=(l+r)/2
    if pd(mid,m):
        r=mid
    else:
        l=mid
print("%.7f"%l)

 day9

贪心算法

常见的贪心问题

  

找零问题

t=[100,50,20,5,1]
n=int(input())
def change(t,a):
    m=[0 for i in range(len(t))]
    for pos,val in enumerate(t):
        m[pos]=a//val
        a%=val
    return m
m=change(t,n)
for i in range(len(m)):
    print(t[i],end=":")
    print(m[i])

 小B同学宿舍

#尽可能使搬运同时进行,以便使单独安排的搬运次数最少。这样用的时间最少,即所用最少时间为不能同时搬运的次数,即某一段走廊使用次数最多。
T=int(input())
s,t=0,0
while T>0:
    ans=0
    zl=[0 for _ in range(205)]
    n=int(input())
    for i in range(n):
        s,t=map(int,input().split())
        s=(s-1)//2
        t=(t-1)//2
        if s>t:
            s,t=t,s
        for j in range(s,t+1):
            zl[j]+=1
    ans=max(zl)
    print(ans*10)
    T-=1

 可拆分背包问题之贪心的自助餐

 

class Food:
    def __init__(self,w,v,dj):
        self.w=w
        self.v=v
        self.dj=dj
n,C=map(int,input().split())
foods=[]
sum_=0
Value=0.0
for i in range(n):
    food=Food(0,0,0)
    food.v,food.w=map(int,input().split())
    food.dj=food.v/food.w
    foods.append(food)
foods.sort(key=lambda f:f.dj,reverse=True)
for i in range(len(foods)):
    sum_+=foods[i].w
if sum_<=C:
    for i in range(len(foods)):
        Value+=foods[i].v
else:
    for i in range(len(foods)):
        if foods[i].w<=C:
            C-=foods[i].w
            Value+=foods[i].v
        else:
            Value+=foods[i].dj*C
            C=0
        if C==0:
            break
print("%.3f"%Value)

快乐司机

n,w=map(int,input().split())
goods=[]
for i in range(n):
    good=list(map(int,input().split()))
    goods.append(good)
goods.sort(key=lambda x : x[1]/x[0],reverse=True)
sum_=0
Value=0
for i in range(n):
    if goods[i][0]<=w:
        Value+=goods[i][1]
        w-=goods[i][0]
    else:
        Value+=(goods[i][1]/goods[i][0])*w
        w=0
    if w==0:
        break
print("%.1f"%Value)

翻硬币

a=list(input())
b=list(input())
ans=0
for i in range(len(a)):
    if a[i]!=b[i]:
        if a[i+1]=='*':
            a[i+1]='o'
        else:
            a[i+1]='*'
        ans+=1
print(ans)

 答疑

n=int(input())
goods=[]
for i in range(n):
    stu=list(map(int,input().split()))
    goods.append(stu)
goods.sort(key=lambda x:x[0]+x[1]+x[2])
# time=[0 for i in range(n+5)]
# time[1]=goods[0][0]+goods[0][1]
# for i in range(1,n):
#     time[i+1]=time[i]+goods[i-1][2]+goods[i][0]+goods[i][1]
# print(sum(time))
time=0
ans=0
for i in goods:
    time+=i[0]+i[1]
    ans+=time
    time+=i[2]
print(ans)

 防御力

n1,n2=map(int,input().split())
A=list(map(int,input().split()))
B=list(map(int,input().split()))
C=list(input())
a=[]
b=[]
for i in range(len(A)):
    a.append((i+1,A[i]))
a.sort(key=lambda x:x[1])
for i in range(len(B)):
    b.append((i+1,B[i]))
b.sort(key=lambda x:x[1],reverse=True)
al=0
bl=0
for i in range(len(C)):
    if C[i]=='0':
        print('A',end='')
        print(a[al][0])
        al+=1
    else:
        print('B',end='')
        print(b[bl][0])
        bl+=1
print('E') 

 day10

动态规划

跳跃

#深度搜索
n,m=map(int,input().split())
a=[[0 for i in range(m+1)] for j in range(n+1)]
for i in range(n):
    a[i+1]=input().split()
    a[i+1]=[0]+list(map(int,a[i+1]))
dx=[1,2,3,0,0,0,1,2,1]
dy=[0,0,0,1,2,3,2,1,1]
ans=0
def dfs(x,y,w):
    global ans
    w=w
    if x==n and y==m:
        ans=max(ans,w)
        return
    else:
        for i in range(9):
            xt=x+dx[i]
            yt=y+dy[i]
            if xt<=n and yt<=m:
                dfs(xt,yt,w+a[xt][yt])
dfs(1,1,a[1][1])
print(ans)

#动态规划
n,m=map(int,input().split())
a=[[0 for i in range(m+1)] for j in range(n+1)]
for i in range(n):
    a[i+1]=input().split()
    a[i+1]=[0]+list(map(int,a[i+1]))
dx=[1,2,3,0,0,0,1,2,1]
dy=[0,0,0,1,2,3,2,1,1]
for i in range(n,0,-1):
    for j in range(m,0,-1):#难以确定前面的走法如何影响后面的走法,那么反过来推,倒推找到走到终点的最大值,前面的走法依赖后面的走法,从而确定起点的走法
        if i==n and j==m:
            continue
        else:
            temp=float('-inf')
            for k in range(9):
                xt=i+dx[k]
                yt=j+dy[k]
                if xt>n or yt>m:
                    continue
                else:
                    temp=max(temp,a[xt][yt])
            a[i][j]+=temp
print(a[1][1])

背包问题初始化细节

0/1背包

n,V=map(int,input().split())
w=[]#体积
v=[]#价值
dp=[0 for i in range(V+1)]
for i in range(n):
    wi,vi=map(int,input().split())
    w.append(wi)
    v.append(vi)
for i in range(n):
    for j in reversed(range(w[i],V+1)):
        dp[j]=max(dp[j],dp[j-w[i]]+v[i])#0/1背包问题是根据上一行更新下一行,用自我滚动减小内存开销
print(dp[V])

 完全背包

与0/1背包的区别就是每种物品有无数种,这时候我们只需要遍历容量J的时候由倒序转变为正序 

n,V=map(int,input().split())
w=[]#体积
v=[]#价值
dp=[0 for i in range(V+1)]
for i in range(n):
    wi,vi=map(int,input().split())
    w.append(wi)
    v.append(vi)
for i in range(n):
    for j in range(w[i],V+1):#完全背包可以选无数个,遍历容量找到可以第i件物品在容量j时的最大价值(和0/1背包的遍历顺序正好相反)
        dp[j]=max(dp[j],dp[j-w[i]]+v[i])
print(dp[V])

多重背包

# n,V=map(int,input().split())
# w=[]#体积
# v=[]#价值
# s=[]
# dp=[0 for i in range(V+1)]
# for i in range(n):
#     wi,vi,si=map(int,input().split())
#     w.append(wi)
#     v.append(vi)
#     s.append(si)
# for i in range(n):
#   for k in range(1,s[i]+1):
#     for j in reversed(range(w[i],V+1)):
#         dp[j]=max(dp[j],dp[j-w[i]]+v[i])
# print(dp[V])
n,V=map(int,input().split())
w=[]#体积
v=[]#价值
s=[]
dp=[0 for i in range(V+1)]
for i in range(n):
    wi,vi,si=map(int,input().split())
    w.append(wi)
    v.append(vi)
    s.append(si)
for i in range(n):
  for j in reversed(range(w[i],V+1)):
    for k in range(1,s[i]+1):
      if j-w[i]*k<0:
        break
      dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i])
print(dp[V])

 

dp=[[0 for i in range(2025)] for j in range(12)]
dp[0][0]=1
for i in range(1,2023):
    for j in reversed(range(1,11)):
        for k in range(i,2023):
            dp[j][k]+=dp[j-1][k-i]
print(dp[10][2022])

 过河卒

dp=[[0 for i in range(30)] for j in range(30)]
dp[2][1]=1
bx,by,mx,my=map(int,input().split())
bx+=2
by+=2
mx+=2
my+=2
flag=[[0 for i in range(by+2)] for j in range(bx+2)]
flag[mx-1][my-2]=1
flag[mx-2][my-1]=1
flag[mx+1][my-2]=1
flag[mx+2][my-1]=1
flag[mx-2][my+1]=1
flag[mx-1][my+2]=1
flag[mx+1][my+2]=1
flag[mx+2][my+1]=1
flag[mx][my]=1
for i in range(2,bx+1):
    for j in range(2,by+1):
        if flag[i][j]==1:
            dp[i][j]=0
        else:
            dp[i][j]=dp[i-1][j]+dp[i][j-1]
print(dp[bx][by])

 最长公共子序列.1

a=list(input())
b=list(input())
#dp[i+1][j+1]表示a以a[i]结尾,b以b[j]结尾的最大公共子序列的长度
#num[i+1][j+1]表示a以a[i]结尾,b以b[j]结尾的最大公共子序列的个数
#1、如果a[i+1]=b[j+1],那么个数就可以直接继承前面的,因为最后这个字符肯定要用,num[i+1][j+1]=num[i][j]
#2、dp[i+1][j+1]=dp[i][j+1] 同样的转移 num[i+1][j+1]+=num[i][j+1]
#3、dp[i+1][j+1]=dp[i+1][j] num[i+1][j+1]+=num[i+1][j]
#4、如果a[i]!=b[j],那计算dp数组,肯定是max(dp[i+1][j],dp[i][j+1]),但要是dp[i+1][j+1]=dp[i][j]的话,就得-num[i][j],因为max(dp[i+1][j],dp[i][j+1])=dp[i][j],就会多计算一次
dp=[[0 for i in range(len(b))] for j in range(len(a))]
num=[[0 for i in range(len(b))] for j in range(len(a))]
for i in range(len(b)):
  num[0][i]=1
for i in range(len(a)):
  num[i][0]=1
for i in range(len(a)-1):
    for j in range(len(b)-1):
        if a[i]==b[j]:#显然a以a[i]结尾,b以b[j]结尾的最长公共子序列即dp[i+1][j+1]为上一状态加1即dp[i][j]+1
            dp[i+1][j+1]=dp[i][j]+1
            num[i+1][j+1]=num[i][j]
        else:#若a[i]!=b[j],有两种状态会转移过来,a以a[i+1]结尾,b以b[j]结尾的最长公共子序列;a以a[i]结尾,b以b[j+1]结尾的最长公共子序列。取最大即可
            dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j])
            if dp[i+1][j+1]==dp[i][j]:
              num[i+1][j+1]-=num[i][j]
        if dp[i+1][j+1]==dp[i][j+1]:
            num[i+1][j+1]+=num[i][j+1]
        if dp[i+1][j+1]==dp[i+1][j]:
            num[i+1][j+1]+=num[i+1][j]

ans1=dp[len(a)-1][len(b)-1]
ans2=num[len(a)-1][len(b)-1]
print(ans1)
print(int(ans2)%int(1e8))

 最长公共子序列.2

a=input()
A=[]
str_a=""
b=input()
B=[]
str_b=""
for i in a:#将蓝肽切割为蓝肽子存储到列表
    if 'A'<=i<='Z':
        if str_a!="":
            A.append(str_a)
            str_a=""
    str_a+=i
A.append(str_a)#将最后一个蓝肽子存储到序列
for i in b:
    if 'A'<=i<='Z':
        if str_b!="":
            B.append(str_b)
            str_b=""
    str_b+=i
B.append(str_b)            
dp=[[0 for i in range(len(B)+5)] for j in range(len(A)+5)]
for i in range(len(A)):#最长公共子序列模板dp[i][j]表示A序列以A[i]结尾,B序列以B[j]结尾的最长公共子序列的长度
    for j in range(len(B)):
        if A[i]==B[j]:
            dp[i+1][j+1]=dp[i][j]+1
        else:
            dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j])
print(dp[len(A)][len(B)])

蓝桥骑士

n=int(input())
a=list(map(int,input().split()))
dp=[1 for i in range(len(a)+5)]#初始化为1,表示以自己结尾的递增子序列的长度为1
#dp[i]表示以a[i]结尾的递增子序列的长度,注意不是最长
ans=0
for i in range(len(a)):
    for j in range(i):
        if a[j]<a[i]:#遍历前i个元素,若小于a[i],那么dp[i]长度由dp[j]+1得来,取最大的那个
            dp[i]=max(dp[i],dp[j]+1)
            ans=max(ans,dp[i])
print(ans)

 以上的动态规划会超时,下面是结合二分法的动态规划

import bisect
n=int(input())
a=[int(i) for i in input().split()]
b=[a[0]]
for i in range(1,n):
    #第1个参数是列表,第2个参数是要查找的数,返回值为>=参数2的第一个索引(即二分法的>=x的最小值),列表中没有该元素,
    #返回插入位置索引
    index=bisect.bisect_left(b,a[i])
    if index==len(b):
        b.append(a[i])
    else:
        b[index]=a[i]
print(len(b))

下面给出一例子:

a=[10,9,2,5,3,7,101,18]

初始时b=[a[0]]=[10]

然后遍历a剩下的元素,去找比b中最后一个元素大的a[i],将它加到b中。a[i]比b的最后一个元素小怎么办呢?如题目所示,a[1]=9,此时index=bisect.bisect_left([10],9) =0,此时会更新b,b=[9]。找下一个a[2]=2,更新b=[2]。找下一个a[3]=5,此时index=bisect.bisect_left([2],5) =1,更新b=[2,5]。找下一个a[4]=3,此时index=bisect.bisect_left([2,5],3) =1,更新b=[2,3]。找下一个a[5]=7,更新b=[2,3,7]。找下一个a[6]=101,更新b=[2,3,7,101]。找下一个a[7]=18,更新b=[2,3,7,18]。

最后b的长度就是最长递增子序列

最优包含 

s=list(input())
t=list(input())
dp=[[0 for i in range(len(t)+5)] for j in range(len(s)+5)]
#dp[i][j]表示s有i个字符,t有j个字符时所需要的最少操作次数
for i in range(1,len(t)+1):
    dp[0][i]=99999#s没有字符,t有字符时,因为只能进行替换操作,因此这种情况操作无数次也不行
for i in range(len(s)):
    for j in range(len(t)):
        if s[i]==t[j]:
            dp[i+1][j+1]=dp[i][j]
        else:
            dp[i+1][j+1]=min(dp[i][j]+1,dp[i][j+1])#要么把s[i]替换,此时操作+1;要么先不考虑s[i],那么此时的操作数为,s有i个字符[0-i),t有j+1个字符[0,j],即dp[i][j+1]的操作数
print(dp[len(s)][len(t)])

合唱队形

n=int(input())
t=list(map(int,input().split()))
dp1=[1 for i in range(n+5)]
dp2=[1 for i in range(n+5)]
for i in range(n):
    for j in range(i):
        if t[i]>t[j]:
            dp1[i]=max(dp1[i],dp1[j]+1)
for i in range(n-1,0,-1):
    for j in range(n-1,i,-1):
        if t[i]>t[j]:
            dp2[i]=max(dp2[i],dp2[j]+1)
ans=0
for i in range(n):
    ans=max(ans,dp1[i]+dp2[i]-1)
print(n-ans)

 路径

import math
def lcm(x,y):
    return x*y/math.gcd(x,y)
dp=[float('inf') for i in range(2022)]#dp[i]表示1-i的距离,初始化为无穷大
dp[1]=0
for i in range(1,2021):
    for j in range(i+1,i+22):
        if j>2021:
            break
        dp[j]=min(dp[j],dp[i]+lcm(i,j))
print(int(dp[2021]))

day11

图论入门

1.数组存边

class edge:
  def __init__(self,f,t,d):
    self.f=f
    self.t=t
    self.d=d
a=[edge(0,0,0) for i in range(n)]
for i in range(n):
  a[i].f,a[i].t,a[i].d=map(int,input().split())

2.最短路问题

弗洛伊德算法

for k in range(1,n+1):
  for i in range(1,n+1):
    for j in range(1,n+1):
      dp[i][j]=min(dp[i][k]+dp[k][j],dp[i][j])

注:只能用于 n < 300 的小规模的图,它可以判断是否有负圈,dp[i][i]<0说明有负圈

弗洛伊德算法+二分法

 

import copy
n,p=map(int,input().split())
g=[[0]*(n+1) for i in range(n+1)]
m=[[0]*(n+1) for i in range(n+1)]
f=[[0]*(n+1) for i in range(n+1)]
for i in range(1,n+1):
    l=list(map(int,input().split()))
    for j in range(1,n+1):
        g[i][j]=l[j-1]
for i in range(1,n+1):
    l=list(map(int,input().split()))
    for j in range(1,n+1):
        m[i][j]=l[j-1]
        f[i][j]=m[i][j]
def floyd(f):
    a=0
    for k in range(1,n+1):
        for i in range(1,n+1):
            for j in range(1,n+1):
                f[i][j]=min(f[i][j],f[i][k]+f[k][j])
    for i in range(1,n+1):
            for j in range(1,n+1):
                a+=f[i][j]
    return a
def check(x,g):
    f=copy.deepcopy(g)
    h=x//n
    s=x%n
    for i in range(1,n+1):
        for j in range(1,n+1):
            if i==j:
                continue
            if i<=s:
                f[i][j]=max(m[i][j],f[i][j]-h-1)
            else:
                f[i][j]=max(m[i][j],f[i][j]-h)
            f[j][i]=f[i][j]#f[j][i]随着f[i][j]一起变化
    return floyd(f)<=p
def slove():
    global f,g,m
    if floyd(f)>p:
        print(-1)
        return
    else:
        l=0
        r=10000000
        while l<r:
            mid=(l+r)//2
            if check(mid,g):
                r=mid
            else:
                l=mid+1
        print(r)
slove()

Dijkstra算法

import heapq#维护pq数组
INF=0x3f3f3f3f3f3f3f3f
n,m=map(int,input().split())
e = [[] for _ in range(m+1)]#数组存边
class Edge:#用于存储边
  def __init__(self,f,to,w):
    self.f=f#起点
    self.to=to#终点
    self.w=w#距离
for i in range(m):
    u, v, w = map(int, input().split())
    e[u].append(Edge(u, v, w))
class SNode:#用于存储中转结点的位置
  def __init__(self,id,n_dis):
    self.id=id
    self.n_dis=n_dis#结点到起点的距离
  def __lt__(self,other):
    return self.n_dis<other.n_dis
pre=[-1]*(n+1)
def print_path(s,t):
  global pre
  if s==t:
    print(s,end=" ")
    return
  print_path(s,pre[t])
  print(t,end=" ")
def dijkstra():
  global pre
  s=1
  done=[0]*(n+1)#记录结点是否被用于优化邻接点过
  dis=[INF]*(n+1)#记录结点与起点的最短距离
  dis[s]=0#起点与起点的最短距离显然为零
  pq=[]#存储中转结点,按该点与起点的距离从小到大排序
  heapq.heappush(pq,SNode(s,dis[s]))#压入起点
  while pq:#中转结点用完程序结束
    u=heapq.heappop(pq)#弹出距离起点最近的点
    if done[u.id]:
      continue#用过则找下一个点
    done[u.id]=1#没用过就标记,接着用它优化邻接点
    for y in e[u.id]:#通过该点找到所有邻接点所在边
      if done[y.to]:#用过就找下一个点
        continue
      if dis[y.to]>y.w+u.n_dis:
        dis[y.to]=y.w+u.n_dis#更新距离
        heapq.heappush(pq,SNode(y.to,dis[y.to]))
        pre[y.to]=u.id
  for i in range(1,n+1):
    if dis[i]>=INF:
      print("-1",end=" ")
    else:
      print(dis[i],end=" ")
  #打印路径
  print()
  for i in range(1,n+1):
      print_path(s,i)
      print()
dijkstra()

spfa算法

INF = 0x3f3f3f3f3f3f3f3f
n, m, s = map(int, input().split())
e = [[] for _ in range(m+1)]#数组存边
class Edge:
    def __init__(self, to, w):#用于存储边
        self.to = to#终点
        self.w = w#距离
for i in range(m):
    u, v, w = map(int, input().split())
    e[u].append(Edge(v, w))
pre=[-1]*(n+1)
def print_path(s,t):
  global pre
  if s==t:
    print(s,end=" ")
    return
  print_path(s,pre[t])
  print(t,end=" ")
def spfa(s):
    global pre
    dist = [INF] * (n+1)#记录结点与起点的最短距离
    inq = [0] * (n+1)#记录结点是否在队列
    dist[s] = 0#起点与起点的最短距离显然为零
    q = []#数组模拟队列
    start=0#队头
    end=0#队尾
    q.append(s)#起点入队
    end+=1#入队即加一个元素
    inq[s] = 1#起点在队列中
    while start!=end:#队列不为空
        u = q[start]#取队头元素进行松弛
        start+=1#删除队列的队头元素
        inq[u] = 0#标记队头出队
        if dist[u] == INF:#队头元素距起点无穷,显然不能用来松弛
            continue#再取队头元素
        for i in range(len(e[u])):#否则取该结点的所有邻接点
            v = e[u][i].to
            w = e[u][i].w
            if dist[v] > dist[u] + w:#松弛所有邻接点
                dist[v] = dist[u] + w
                pre[v]=u
                if inq[v] == 0:
                    q.append(v)#松弛后的邻接点入队
                    end+=1
                    inq[v] = 1
    for i in range(1, n+1):
      if dist[i] == INF:
          print("-1", end=" ")
      else:
          print(dist[i], end=" ")
    print()
    for i in range(1,n+1):
      print_path(s,i)
      print()
spfa(s)

3.最小生成树

prim算法

inf=0x3f3f3f3f3f3f3f3f
demo=[]
m,n=map(int,input().split())#m个点,n条边
closest=[0]*m
lowcost=[0]*m
G=[[inf] * m for i in range(m)]
for i in range(n):
    a,b,c=map(int,input().split())
    G[a][b]=G[b][a]=c#邻接矩阵存储图
def prim():
    global closest,lowcost,G,m
    for i in range(m):
        lowcost[i]=inf#候选最短边的权值,初始化为无穷大
        closest[i]=0#候选点状态,值不为-1表示点在集合V中
    closest[0]=-1#将起点放入集合U中
    num=0
    ans=0
    e=0#表示最新加入集合U的点
    while num<m-1:#最小生成树有顶点数减一条边,加入m-1条边
        micost=inf#用于记录连接U,V集合的最短边
        miedge=-1#用于记录连接U,V集合的最短边的V集合中的那个点
        for i in range(m):
            if closest[i]!=-1:#该点不在集合U中
                temp=G[e][i]#在邻接矩阵中找到该点与最新点的距离
                if temp<lowcost[i]:#如果距离比候选最短边小
                    lowcost[i]=temp#更新U-V集合中点的距离
                    closest[i]=e#可以存储此时最小生成树某条边的邻接点
                if lowcost[i]<micost:#找到当前连接U,V的最短边的权值和,连接点
                    miedge=i
                    micost=lowcost[miedge]
        ans+=micost#更新总权值
        demo.append(micost)#存储权值
        e=miedge#更新当前U集合最新点
        closest[e] = -1
        num+=1
    return ans#最小生成树的总权值
print(prim())
for i in range(m-1):
    print(demo[i],end=" ")

 kruskal 算法

n,m=map(int,input().split())#n个点,m条边
father=[i for i in range(n+1)]#存储父节点
Q=[]#存储边信息
#cont=0
sum=0#最小生成树权值和
st=0#记录已经连接的边数
for i in range(m):
    x,y,k=map(int,input().split())
    Q.append({'x':x,'y':y,'k':k})
    #cont+=k
Q.sort(key=lambda x:x['k'])#按边的权值从小到大排序
def find(x):#并查集查找根节点
    if father[x]==x:
        return x
    father[x]=find(father[x])#记忆存储
    return father[x]
for i in range(m):
    tx=find(Q[i]['x'])#该边起点的父节点
    ty=find(Q[i]['y'])#该边终点的父节点
    if tx!=ty:#父节点不同,则属于两个连通分量,可以连接
        sum+=Q[i]['k']#修改最小生成树权值和
        st+=1
        father[tx]=ty#合并两个连通分量
        if st ==n-1:#最小生成树n-1条边
            break
print(sum)

4.最近公共祖先

from collections import defaultdict
n, m = map(int, input().split())#n个电脑,m个问题
gra = defaultdict(list)#存储树结构
d = [0] * 100010#存储出度,即延时时间
for i in range(n - 1):
    a, b = map(int, input().split())#a,b电脑相连
    gra[a].append(b)#连接a的有[b,...]
    gra[b].append(a)
    d[a] += 1#出度+1
    d[b] += 1
query = [[] for _ in range(100010)]#存储问题信息query[u]=[(v,i)]
res = [0] * 100010#存储答案
for i in range(m):
    a, b = map(int, input().split())
    if a != b:
        query[a].append((b, i))
        query[b].append((a, i))
    else:
        res[i] = d[a]
q = [0] * 100010#父节点
for i in range(1, n + 1):
    q[i] = i
w = [0] * 100010#i点到起点的延时时间
st = [0] * 100010#tarjan算法标记数组
def find(x):
    if x != q[x]:
        q[x] = find(q[x])
    return q[x]
def dfs(u, fa):
    w[u] += d[u]#u结点到根节点的延迟时间
    for g in gra[u]:#查找连接u的所有点
        if g == fa:#排除父节点
            continue
        w[g] += w[u]#g结点到根节点的延迟时间
        dfs(g, u)
def tarjan(u):
    st[u] = 1#入u时标记
    for j in gra[u]:#枚举u的孩子
        if st[j] == 0:#孩子没走过
            tarjan(j)#则进入孩子
            q[j] = u#回u时孩子指向父亲
    for item in query[u]:#离u时枚举以u为起点的问题的终点
        y, id = item
        if st[y] :#问题的终点走过即建树,那么可以找到公共祖先
            anc = find(y)
            #时间延迟为两点到起点时间延迟和减去最近公共祖先到
            #起点的时间延迟的两倍最后加上最近公共祖先的出度
            res[id] = w[y] + w[u] - w[anc] * 2 + d[anc]
dfs(1, -1)#深搜得到w数组
tarjan(1)#根节点开始
for i in range(m):
    print(res[i])

 day12

简单数论

1.取模运算

a**n mod b = (a mod b)**n mod b

2.快速幂

b,p,k=map(int,input().split())
def fast_pow(a,n,m):
    res=1
    a=a%m
    while n>0:
        if n&1==1:
            res=res*a%m
        a=a*a%m
        n>>=1
    return res
print(fast_pow(b,p,k))

 3.最大公约数和最小公倍数

gcd(a,b)=gcd(|a|,|b|)

gcd(a,b)=gcd(a,a+b)=gcd(a,ka+b)

gcd(ka,kb)=k*gcd(a,b)

gcd(a,b,c)=gcd(gcd(a,b),c)

gcd(a,b)=d,则gcd(a/d,b/d)=1,即a/d与b/d互素

gcd(a+cb,b)=gcd(a,b)

lcm(a,b)=a/gcd(a,b)*b

def gcd(a,b):
    if b==0:
        return a
    else:
        return gcd(b,a%b)
def lcm(a,b):
    return a/gcd(a,b)*b#先除防溢出
a,b,c=map(int,input().split())
k=lcm(a,b)
print(int(lcm(k, c)))

 4.质数

n=int(input())
if n%2==1:
    print(n*(n-1)*(n-2))
if n%2==0:
    if n%3==0:
        print((n-1)*(n-2)*(n-3))
    else:
        print(n*(n-1)*(n-3))

 5.埃氏筛和欧式筛

n=int(input())
primes=[]
bprimes=[0]*(n+1)
bprimes[0]=1
bprimes[1]=1
cnt=0
def getprimes(n):
    global primes,bprimes,cnt
    for i in range(2,n+1):
        if not bprimes[i]:
            primes.append(i)
            cnt+=1
            #对于i*p,如果p<i,那么它一定被p标记过,可以进一步优化,从i*i开始枚举
            for j in range(i*2,n+1,i):
                bprimes[j]=1
getprimes(n)
if len(primes)!=0:
    print(*primes)
    print(cnt)
else:
    print(cnt)
n=int(input())
primes=[]
bprimes=[0]*(n+1)
bprimes[0]=1
bprimes[1]=1
cnt=0
def getprimes(n):
    global primes,bprimes,cnt
    for i in range(2,n+1):
        if not bprimes[i]:
            primes.append(i)
            cnt+=1
        j=0
        while j<cnt and i*primes[j]<=n:
            bprimes[i*primes[j]]=1
            if i%primes[j]==0:
                break
            j+=1
getprimes(n)
if len(primes)!=0:
    print(*primes)
    print(cnt)
else:
    print(cnt)

6.分解质因子

p=[0]*30
c=[0]*30
def factor(n):
    m=0
    for i in range(2,int(n**0.5)+1):
        if n%i==0:
            m+=1
            p[m]=i
            c[m]=0
        while n%i==0:
            n=n//i
            c[m]+=1
    if n>1:
        m+=1
        p[m]=n
        c[m]=1
    return m
a,b=map(int,input().split())
for i in range(a,b+1):
    m=factor(i)
    print("{}=".format(i),end='')
    for j in range(1,m+1):
        for k in range(1,c[j]+1):
            print(p[j],end='')
            if k<c[j]:
                print('*',end='')
        if j<m:
            print('*',end='')
    print()

day13

组合数学

1.乘法原理

2.排列数

3.组合数

4.计数dp

import sys
dp=[0]*21
dp[0]=0
dp[1]=0
dp[2]=1
for i in range(3,21):
  dp[i]=(i-1)*(dp[i-2]+dp[i-1])
for n in sys.stdin:#多行输入
  n=int(n)
  print(dp[n])

 5.鸽巢原理

6.杨辉三角形

n=int(input())
def C(a,b):#求组合数
    res=1
    i=a
    j=1
    while j<=b:
        res=int(res*i/j)
        if res>n:
            return int(res)#大于目标值提前结束
        i-=1
        j+=1
    return int(res)
for k in range(16,-1,-1):#第33行16列为1166803110,大于1000000000
    l=2*k#因此目标在16列之前
    r=max(n,l)#如果目标值很小则看这列的第一个值
    res=-1
    while l<=r:#二分该列的行号
        mid=(l+r)//2
        if C(mid,k)>=n:
            res=mid
            r=mid-1
        else:
            l=mid+1
    if C(res,k)==n:#找到则打印,否则找下一列
        print((res+1)*res//2+k+1)
        break

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小帅乱撞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值