十五届蓝桥杯备战

1. datatime模块

  1. 熟练掌握datatime包的一些函数,能在在处理时间模拟这一类型的题型更加快速。
  • datetime.datetime
    • 这个模块下的两个函数
    • strftime(“%Y-%m-%d %H:%M:%S”) 转换成怎样格式的字符串
    • strptime(str,格式) 这歌字符串是怎样的格式,告诉了才能知道怎么被解析
  • datetime.date
  • datetime.timedelta(时间)
  • datetime.timestample() 可以全部转换成秒

2. 倍增思想 ST表

  1. RMQ问题
    在这里插入图片描述

  1. ST表
    在这里插入图片描述

  • 解决可重复贡献问题
    在这里插入图片描述

  • 动态规划的思想,区间的最值问题拆解分为两个子区间的最值,状态进行转移

  • 由于动态规划要求解的当前状态 依赖于已经求出的状态, 所以需要先枚举j从小到大进行枚举即可

  • st表只要懂得递推式
    在这里插入图片描述

  • 预处理出st表,然后每次询问拆解成二进制的形式


# 主要是状态转移
# i+len-1=j
# i=j-len+1 细节方面需要考虑
def st_init():
	#预处理对应的二进制
	l=math.ceil(math.log2(n))+1
	# 
	dp=[[0]*l for i in range(n)]
	for i in range(n):
		dp[i][0]=num_list[i]
	# 进行递推处理 根据动态规划具备后效性,一定要保证,更新是正确的,要调整循环次序问题,这个也是挺关键的
	for j in range(1,l):
		pj=1<<(j-1)
		for i in range(n):
			dp[i][j]=max(dp[i][j-1],dp[i+2^(j-1)][j-1])	
	return dp
def query(l,r):
	#拆解成二进制 这个可以 通过数学证明,一定保证完全覆盖区间
	#而且右边界一定在左边界 右边
	s=int(math.log2(r-l+1))
	#主要是细节方面
	return max(dp[l][s],dp[r-2^(s)+1][s])

  1. 还有lca也运用了倍增的思想
  • p[u][i]数组 表示节点 u往上走2^i步能走到的节点
  • 倍增思想好多都是这么一个 状态定义方式
def dfs(u,fa):
	deep[u]=deep[fa]+1
	p[u][0]=fa
	for  i in range(1,21):
		#从小到大进行转移
		p[u][i]=p[p[u][i-1]][i-1] #
	for v in G[u]:
		if v==fa:
			continue
		dfs(v,fa)

# lca求法
# 保证x更深,做一个比较,如果不满组,则交换
# 从大到小进行枚举,如果深度不相同,则更新x节点
# 循环结束,如果二者编号相同,那么结束
# 否则开始找 lca,从大到小枚举
# 由于保证最最近共公共祖先,所以判断条件是p[x][i]!=p[y][i]

2. bisect模块

  1. 当对于一个单调不减(不增)的数列进行查找下标索引时,bisect模块真的非常好用
    • bisect.bisect 找到大于等于的下标索引
    • bisect.bisect_right 找到大于的下标索引
    • bisect.insort() 查找并插入元素
    • bisect.insort_letf()
      在这里插入图片描述

3. 马拉车算法

  1. 最长回文子串概述
    在这里插入图片描述

  1. 马拉车算法 细节处理
    在这里插入图片描述

  1. 理解马拉车算法需要理解的几个概念
    • dp数组:以i为中心可扩展的长度
    • center,right 维护当前的最右回文子串
      在这里插入图片描述
  • 分为三种情况
    • 第一种i不在回文区间中,直接暴力求解dp[i],向两端进行扩展
    • 第二种情况,i在回文区间中,并且right-i>=dp[对称点],这种情况直接dp[i]=dp[对称]
    • 第三种情况,i在回文区间中,但是right-i<dp[对称点],这种情况要从right,2*i-right 继续向两端扩展
    • 通过发现 第二种情况和第三种情况 其实进行一个 合并,合并成为一种情况,在right-i 和 dp[对称点] 取一个最小值,然后进行扩展

  1. 模板代码
# 首先是向两端进行扩展
def extend(s,l,r):
	while l>=0 and r<len(s) and s[l]==s[r]:
		l-=1
		r+=1
	# 因为循环结束了,还向两端进行移动了,所以减去2
	return (r-l-2)//2
def malaceh(s):
	# 对s进行重新扩展
	s="#"+"#".join(s)+"#"
	# 维护最右回文子串
	cnter,right=0,0
	dp=[0]*len(s)
	for i in range(1,len(s)):
		#第一种情况进行判定
		if i>right:
			dp[i]=extend(s,i,i)
		else:
			#两种情况进行的一个合并
			i_sys=2*center-i
			mis_dis=min(dp[i_sys],right-1)
			dp[i]=extend(s,i-mis_dis,i+mis_dis)
		
		#维护最新的 右回文子串
		if i+dp[i]>right:
			center,right=i,i+dp[i]
	return max(dp)

  1. 马拉车模板题目

  1. 有限制的马拉车题目

4. KMP算法

  1. kmp算法概述-next数组的含义
    在这里插入图片描述

  1. next数组的求法
    在这里插入图片描述
  • 类似于递推式,next数组就类似于 逻辑上的双指针 ,还是非常容易进行一个相关的理解
  • next数组的理解看作是 逻辑上的双指针,就非常容易理解
  • kmp中,明白i指向s串,j才是t串即可

  1. kmp模板题目
next=[0]*10000010
def get_next(t):
	#因为next[i]表示的是0-(i-1)的最长前后缀 不包含本身,所以i从1开始
	for i in range(1,len(t)):
		j=next[i]
	while j>0 and t[i]!=t[j]:
		j=next[j]
	if t[i]==t[j]:
		next[i+1]=j+1
	else:
		next[i+1]=0
def kmp(s,t):
	#返回s中t出现的次数
	ans=0
	j=0
	for i in range(len(s)):
		while j>0 and s[i]!=t[j]:
			j=next[j]
		if s[i]==t[j]:
			j+=1
		if j==len(t):
			ans+=1
			j=next[j]
	return ans


  1. [契合匹配 kmp算法的应用]()

5.字典树

  1. 首先学习一种比较简单而且特殊的字典树,01字典树
    在这里插入图片描述

  • 01字典树 是二叉树,每个节点的权值只能是0或者1,也就是说边只能是0或者1
  • 所以能够非常好的解决二进制相关问题

  1. 问题引入:
    在这里插入图片描述
  • 给定n个数,求两个数异或的最大值:
    • 常规做法就是两重循环
    • 字典树+贪心 完全就可以优化 (字典树的深度取决于树的范围,要使得异或值最大,贪心即可,找到相反的一条链即是最大值) 贪心+逐位确定

def insert(x):
	global count
	now=0
	for j in range(31,-1,0):
		pos=(x>>j)&1
		if not son[now][pos]:
			count+=1
			son[now][pos]==count
		now=son[now][pos]
	# 下标 进行一个存储
	val[now]=x

def query(x):
	# 理解为头节点
	now=0
	for j in range(31,-1):
		pos=(x>>j)&1
		# 进行判断是否存在这个路径
		if son[now][pos^1]:
			#存在的话,跑到一层去
			now=sonp[now][pos^1]
		else:
			now=son[now][pos]
	# 返回 下标对应的数字
	return val[now]

son=[[0]*2 for i in range(21000)]
val=[0]*21000
count=0
n=int(input())
num_list=list(map(int,input().split()))
for i in num_list:
	insert(i)

  • 这段代码进行一个相关的理解的话,很关键的点,就是每次创建一个节点(逻辑上的)count+1,这个count计数变量是非常的关键
  • 对于son数组 第一维开多大,取决于一共要创建多少个节点这个要进行一个理解方面的
  • 就是根据二维数组的值是否存在 来进行判断 当前是否存在路径

  1. 例题:给定一棵树和树上的边权,求两点之间路径异或值的最大值(这个是模板题目上面套了一层)
    在这里插入图片描述
  • 首先第一步,知道异或的性质 A^A=0 ,所以可以转换成 每个节点到根节点的异或值(通过dfs处理出来)转换成 两个节点的异或值
  • 两个节点的异或值的最大值,显然可以通过 tire 树 进行一个相关方面的求解
#include<bits/stdc++.h>
using namespace std;
#define maxn 110000
int trie[maxn*30][2]
int val[maxn],n,head[maxn],cnt;
strcut Edge{
	int nex,to,dis;
}edge[maxn<<1];

void add(int from,int to,int dis)
{
	edge[++cnt].nex=head[from];
	head[from]=cnt;
	edge[cnt].to=to;
	edge[cnt].dis=dis;
	return ;
		}
void dfs(int x,int fa)
{
	for(int i=head[x];i;i=edge[i].nex)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		x0[v]=x0[x]^edge[i].dis;
		dfs(v,x)
		
	}
	return;
}

  1. 字典树
    在这里插入图片描述
  • 字典树两种实现方式,一种是基于类的方法,另外一种是数组模拟的方法
  • 基于类的方法
class TreeNode():
	def __init__(self):
		#nodes表示从当前节点往下走的后续节点
		self.nodes={}
		#if_leaf表示是否为字符串终止标志
		self.is_leaf=False
	
	#输入字符串s # 插入的时候,都是从虚拟节点开始,所以curr=self
	def insert(self,s):
		#获取当前节点
		curr=self
		for c in s:
			if c not in curr.nodes.keys():
				curr.nodes[c]=TreeNode()
			curr=curr.nodes[c]
		#记录字符串结束标志
		curr.is_leaf=True
	# 判断前缀s 是否存在
	def pre(self,s):
		#当前节点
		curr=self
		for c in s:
			if c not in curr.nodes.keys():
				return False
			curr=curr.nodes[c]
		return True
	


  • 基于二维数组 的形式 基于二维数组的方式,一定要考虑是否会超内存的问题,一般一维在1e5,就不会超内存,这一点一定要进行相关的注意
    • 二维数组 一维 二维 各自都是具备各自的含义的,这一点要知道
    • 第一维 一般就是 节点的个数
    • 第二位就是 子节点的 含义
    • 二维数组的值 就是 节点的个数

  1. 前缀判定

  2. 字典树的模板题目

N=int(1e5)+10

son=[[0]*26 for i in range(N)]
cnt=[0]*N  # 维护的是当前节点 经过了多少个字符串 节点上进行 相关维护
count=0


def insert(s,v):
  global count
  p=0
  for c in s:
    cnt[p]+=v
    u=ord(c)-ord("a")
    if son[p][u]==0:
      count+=1
      son[p][u]=count
    p=son[p][u]
  #不要忘记,最后这个p也要进行次数记录
  cnt[p]+=v

def query(s):
  p=0
  ans=0
  for c in s:
    u=ord(c)-ord("a")
    if son[p][u]==0:
      # 没有路了,直接返回
      return ans
    p=son[p][u]
    # 如果当前节点 属性值为0,表示不是前缀
    if cnt[p]==0:
      return ans
    ans+=1
  return ans

n=int(input())
ss=[]
for i in range(n):
  strs=input()
  insert(strs,1)
  ss.append(strs)
for i in ss:
  insert(i,-1)
  print(query(i))
  insert(i,1)

6. 单调栈单调队列

  1. 单调栈的应用:给定一个数组,求出数组中每个数左边或者右边第一个比它大或者小的数
  • 首先需要搞清楚,需要维护的是什么,需要维护一个单调递增的单调栈还是一个单调递减的单调栈,这个需要认真分析一下过程

  • 如果要求取每一个数的左边第一个比之大的数,那么应该维护一个单调递减的单调栈

  • 如果要求取每一个数的左边第一个比之小的数,那么应该维护一个单调递增的单调栈

  • 如果求取数组中每一个数 的右边第一个比之大的数,或者小的数,那么进行一个思维上的转换即可,将这个数组进行逆序操作,转换成 左边,减少分析过程


  1. 单调栈模板题目
    在这里插入图片描述

# 单调栈的模板题目
# 很是经典,非常经典,很是经典
# 这个输出的是编号,而不是具体的值,所以需要进行一个注意
#
N=int(input())
num_list=list(map(int,input().split()))

# 初始化一个单调栈,存储楼房的编号
#  求左边第一个比它高的楼房是哪个,所以维护一个单调递减的单调栈
# 判断的依据就是 当前元素与栈顶元素做比较,a[i]>=stack[-1]

# 遍历每一个楼房
# 存储答案 不一样
# 逻辑有点问题,还是没能够解决这些东西
def get_zuogao():
    stack = []
    ans=[]
    for i in range(N):
        while stack and num_list[i]>=num_list[stack[-1]]:
            #进行出栈操作
            stack.pop()
        if not stack:
            ans.append(-1)
        else:
            ans.append(stack[-1]+1)
        stack.append(i)

    print(*ans)

def get_yougao():
    stack=[]
    ans=[]
    #求右边第一个比之高的数
    #转换思路--将数组 逆序——然后转换成求左边 第一高的
    # 还是维护一个单调递减的单调栈
    num_temp=num_list[::-1]

    # 因为输出的是 编号,对数组进行了一个逆序操作
    # 但是 要保留原来的 编号,所以可以开一个字典进行记录
    dicts={}
    for i in range(N):
        dicts[i]=N-i-1

    #

    for i in range(N):
        while stack and num_temp[i]>=num_temp[stack[-1]]:
            #进行出队操作
            stack.pop()
        if not stack:
            #表明了
            ans.append(-1)
        else:
            ans.append(dicts[stack[-1]]+1)
        stack.append(i)
    print(*ans[::-1])

get_zuogao()
get_yougao()



  1. 单调队列的应用:求给定滑动窗口的最值问题
  • 可以求取一维窗口的最值问题
  • 也可以求取二维 滑动窗口的最值问题
  • 单调队列和单调栈 原理都是一样的,基于暴力的情况 挖掘一些性质(单调栈,队列中某一些元素永远不会作为答案输出,)然后一些相关的优化

# 单调队列,采用deque
from collections import deque

# 给定一个长度为n的数组,然后求取区间长度为k的的子区间的最大值和最小值
n,k=map(int,input().split())
num_list=list(map(int,input().split()))
q=deque()

# 遍历每一个元素
for i in range(n):
    #q[0]表示队首 q[-1] 表示对尾
    if q and i-k+1>q[0]:
        #表明队头滑出窗口,说明,要删除q[0]
        q.popleft()
    # 如果要维护区间最大值的话,那么就需要维护一个 单调递减的单调队列
    # 如果当前元素 与对尾元素 进行一个比较发现 num_list[i]>=q[-1] 那么就要出队
    while q and num_list[i]>=num_list[q[-1]]:
        q.pop()
    #当前这个数的下标 进队列
    q.append(i)
    if i>=k-1:
        print("%d"%num_list[q[0]])
  • 相较于单调栈,单调队列就是多出了两点
    • 一个是需要判断队头是否出队 要通过 i-k+1>q[0]做比较
    • 还有一个是 什么时候输出 i-k+1>=0 的时候才能进行输出
    • 这两个细节方面要进行 一个相关方面的考虑

7. 数论

7.1 同余

  1. 同余的概念
    在这里插入图片描述

  1. 同余的性质
    在这里插入图片描述
  • 对于加减乘 这三种运算,交换和模运算的 运算顺序对 最后的结果不会产生影响的
  • 如果是除法的话,那么交换运算顺序 就会对最后的结果产生影响,那么这个时候,就需要引入 乘法逆元,将除以这个数,转换成 乘以这个数的逆元,这样就不会有影响

  1. 求解逆元的几种方法
    • 扩展的欧几里得算法 求解逆元的通用做法,不管在mod 什么意义下,都可以用这个进行一个相关的求解过程,还是非常可以的
    • 费马小定理:加了约束条件,满足了这些约束条件,就可以用 快速幂快速 求解逆元
      • mod的数必须是 素数
      • 求乘法逆元的数 必须 与 模的数 互质 即gcd(a,n)==1

7.2 素数筛

  1. 埃及筛法
def get_prime(n):
	vis=[0]*(n+1)
	prime=[]
	for i in range(2,n+1):
		if vis[i]==0:
			prime.append(i)
			for j in range(i+i,n+1,i):
				vis[j]=1
	return prime

  1. 线性筛法
  • 线性筛法 就是为了解决埃氏筛法 重复筛的过程,进一步降低了时间复杂度
  • 埃是筛法 是利用当前质数的所有倍数 进行晒出掉,注意,不同质数的所有倍数当中,会有重叠的部分,所以会有重复筛的过程
  • 但是线性筛法的过程,不是利用当前质数的所有倍数进行晒出,而是利用当前质数表中的所有质数的 i倍 进行晒出掉
  • 一个变动的是 质数的倍数,一个变动的是质数列表中的质数
  • 当然还需要进行一个特判
  • 这个过程还是很简单的一个小的过程
def get_prime(n):
	prime=[]
	vis=[0]*int(1e5)
	for i in range(2,n):
		if not vis[i]:
			prime.append(i)
		#此时变动的不是质数的倍数,而是质数列表
		j=0
		while prime[j]*i<=n:
			vis[prime[j]*i]=1
			if i%prime[j]==0:
				break
			j+=1

7.3 快速幂

  1. 递归模板
def ksm(a,b,c):
	if b==0:
		return 1
	if b==1:
		return 1
	ans=ksm(a,b//2,c)
	#核心步骤:反复平方法
	ans=ans*ans%c
	#判断当前b基偶性
	if b:
		ans=ans*a%c
	return ans%c


  1. 二进制拆分模板
def ksm(a,b,c):
	ans=1
	while b:
		if b&1:
			ans=ans*a%c
		b=b>>1
		a=a*a%c
	return ans%c

  1. 矩阵快速幂 (快速幂的推广)
def mul(a,b):
	m,n=len(a),len(a[0])
	x,y=len(b),len(b[0])
	if n!=x:
		return False
	c=[[0]*y for i in range(m]
	for i in range(m):
		for j in range(y):
			for t in range(x):
				c[i][j]+=a[i][t]*b[t][j]
	return c

def ksm(a,b):
	ans=[[1 if i==j else 0 for j in range(n)] for i in range(n)]
	while b:
		if b&1:
			ans=mul(ans,a)
		a=mul(a,a)
		b=b>>1
	returnb ans

	

  1. 矩阵快速幂–斐波那契数列
    例题说明

在这里插入图片描述

# 这个属于最简单的矩阵快速幂,递推式已经给出
# 但实际情况下,递推式往往需要 自己列出,然后转换成矩阵递推式
# 建模成矩阵乘法,才能使用快速幂进行加速

mod=1000000007

def mul(a,b):
  x,y=len(a),len(a[0])
  m,n=len(b),len(b[0])
  c=[[0]*2 for i in range(2)]
  for i in range(x):
    for j in range(n):
      for k in range(m):
        c[i][j]=(c[i][j]+a[i][k]*b[k][j])%mod
  return c
def qsm(a,b):
  ans=[[1,0],[0,1]]
  while b:
    if b&1:
      ans=mul(ans,a)
    a=mul(a,a)
    b=b>>1
  return ans

def output(a):
  for x in a:
    print(" ".join(map(str,x)))
# 构建矩阵
a=[[1,1],[1,0]]
b=[[1],[1]]


# 开始处理
t=int(input())
for i in range(t):
  n=int(input())
  if n==1 or n==2:
    print(1)
  else:
    count=n-2
    print(mul(qsm(a,count),b)[0][0])


7.4 唯一分解定理

  1. 唯一分解定理,也称为算术基本定理
    在这里插入图片描述

# 优化:根号+提前停止,要进行一个判断 当已经是1的时候,提前进行一个推出
# 其实就是 就是试 除法的的修i该
def fenjie(n):
	temp=int(math.sqrt(n))+1
	dicts={}
	for i in range(2,temp+1):
		#对于每一个质数进行判断处理
	
		count=0
		flag=0
		while n%i==0:
			count+=1
			n//=i
			flag=1
		# 每一轮都要进行一个判断
		if flag:
			dicts[i]=dicts.get(0,i)+count
		if n==1:
			break
	if n>1:
		#说明还存在一个 质数
		dicts[n]=dicts.get(0,n)+1
  1. 唯一分解定理的性质 以及应用
  • 因子个数
  • 因子之和
    在这里插入图片描述
    • 快速求取 因子之和的方法
    • 就是等比数列进行求和的方法,就可以快速求解得到因子之和

  1. 阶乘的约数
  • 第一种方法 首先 就是想到了 质因数分解 得到各个质因数 以及对应的 幂次个数,然后运用唯一分解定理的结论,快速求和约数之和,但是时间复杂度会达到 n n n\sqrt{n} nn python 会有一个点过不了。而且需要注意的是,等比数列求和的时候,会有除法操作,也就是说,这是模数意义下的除法操作,模数意义下的除法操作,不能直接操作,否则最终的答案是错误的,这就需要引入逆元的求解
  • 第一种方法的时间复杂度主要是 对每一个数(1-n)都进行了质因数分解,才得到对应的幂次,有没有一种更加快捷的方法求得 幂次,数学上是有的,比如说n=20,那么20的阶乘 会提供2 多少个幂次 通过 20/2 10/2 5/2 2/2 也就是 10+5+2+1 18个幂次,这样就能快速求解出来幂次,优化时间复杂度

  1. 代码如下:
# 第一种做法
def ksm(a,b,mod):
  ans=1
  while b:
    if b&1:
      ans=ans*a%mod
    a=a*a%mod
    b>>=1
  return ans
def fenjie(x):
  temp=int(math.sqrt(x))
  for i in range(2,temp+1):
    count=0
    flag=0
    while x%i==0:
      count+=1
      x//=i
      flag=1
    if flag:
      dicts[i]=dicts.get(i,0)+count
  # 这里一个很小的修改,没想到会带来这么大的麻烦,真的yao'jin'x
    if x==1:
      break
  if x>1:
    dicts[x]=dicts.get(x,0)+1
mod=998244353
n=int(input())
dicts={}
for i in range(2,n+1):
  fenjie(i)

# 得到了 质因数和 个数 的关系,然后用等比求和公式进行求和
ans=1
for prime,times in dicts.items():

  ans=ans*(ksm(prime,times+1,mod)-1)%mod*ksm(prime-1,mod-2,mod)%mod
  
print(ans)

# 第二种做法
def ksm(a,b,mod):
	ans=1
	while b:
		if b&1:
			ans=ans*a%mod
		a=a*a%mod
		b>>=1
	return ans

def get_prime(n):
	vis=[0]*(n+1)
	prime=[]
	for i in range(2,n+1):
		if not vis[i]: #表明是素数
			prime.append(i)
		# 用素数的倍数筛
		for j in range(i*i,n+1,i):
			vis[j]=1
	return prime
mod=998244353
n=int(input())
prime=get_prime(n)

#快速统计质约数的个数
ans=1
for p in prime:
	res=0
	t=n
	while t:
		res+=t//p
		t//=p
	ans=(ans*(ksm(p,res+1,mod)-1)%mod*ksm(p-1,mod-2,mod)%mod)%mod

print(ans)

7.5逆元

  1. 扩展的欧几里得算法 求解逆元 是基于 裴蜀定理
  • 就是通过构造解这个过程,来进行操作的
def exgcd(a,b):
	#递归出口
	if b==0:
		return a,1,0
	g,x2,y2=exgcd(b,a%b)
	# 根据推出的关系式得到
	x1,y1=y2,x2-(a//b)*y2
	return g,x1,y1

def Func(a,b,m):
	g,x1,y1=exgcd(a,b)
	if m%g!=0:
		return None,None,None
	x0,y0=x1*m//g,y1*m//g
	return g,x0,y0

def Inv(a,n):
	# 为了保证得到的逆元在0-n-1 之间,所以还要加上这么一个函数
	g,x,y=Func(a,n,1)
	if x is None:
		return None
	return (x%n+n)%n

  1. 通过费马小定理 求解逆元,接非常快速,但是具备前提条件、
    • 首先mod的数一定是 素数
    • 其次,gcd(a,n)==1 也就是说 要求逆元的数·与 mod数 互质
    • 这个时候a的逆元 就是 a的n-2 次方,记住这个结论

7.6 欧拉函数和欧拉降幂

  1. 欧拉函数定义:表示小于等于n中和n互质的数字个数

    • 小于等于n
    • 互质
  2. 欧拉函数的性质以及一些说明
    在这里插入图片描述

在这里插入代码片

  1. 如何求取欧拉函数,利用唯一分解定理
    在这里插入图片描述
  • 很明显可以看出 欧拉函数的求取,只与质因数有关,与质数无关
def get_phi(n):
	abs=n
	m=int(n**0.5)
	# 这个循环 知识枚举可能存在 的 质因数 这个方面要进行一个理解
	for i in range(2,m+1):
		if n%i==0:
			ans=ans//i*(i-1)
		while n%i==0:
			n//=i
	# 因为只遍历了根号,所以还有可能存在一个大于根号n的质因数,所以特判
	# 如果
	if n>1:
		ans=ans//n*(n-1)
	return ans

  1. 欧拉定理
    在这里插入图片描述
    • 费马小定理是欧拉定理的特殊
    • 一般都是 用第三个分式,是通式,通过欧拉定理降幂之后,在采用快速幂的模板,就可以快速求解,这个满足的硬性条件就是b> ϕ ( p ) \phi(p) ϕ(p)

在这里插入图片描述

def ksm(a,b,c):
	ans=1%c
	while b:
		if b&1:
			ans=ans*a%c
		a=a*a%c
		b=b>>1
	return ans%c

n,m=map(int,input().split())
mod=int(1e9)+7
# 因为是质数,所以欧拉函数
phi=mod-1
flag=0
for i in range(1,m+1):
	x=x*i
	if x>=phi:
		# 就可以进行降幂操作
		x=x%phi
		flag=1
if flag:
	# 根据公式得到
	x+=phi
print(ksm(n,x,mod))

8. 树

  1. 树的存储方式:一般采用邻接表的形式

  1. 树的遍历 dfs遍历
  • 树的遍历 并不需要判重vis 数组,图的遍历需要 判重数组vis
# 单向边
def dfs(u):
	print(u)
	for v in Tree[u]:
		dfs(v)
dfs(root)

# 双向边
def dfs(u,fa):
	print(u)
	for v in Tree[u]:
		if v==fa:
			continue
		dfs(v,u)
dfs(root,-1)


  1. BFS遍历层序遍历
from collections import deque
#bfs遍历模板
def bfs():
	result=[]
	queue=deque()
	#先入队
	queue.append(root)
	while queue:
		u=queue.popleft() #队首元素出队
		result.append(u)
		for v in Tree[u]:
			queue.append(v)
	return result
  • 例题 说明
# 只有边没有说明依赖关系,没有说明父亲儿子关系,都当作无向边 进行处理
# 从根节点出发 遍历到叶子节点,每遍历到一个节点 就要把当前节点的字符添加上
# 就是一个遍历
def dfs(u,fa):
	path.append(s[u])
	#进行判断是否是叶子
	flag=True
	for v in Tree[u]:
	# 进行到这个循环说明不是叶子节点
		if v==fa:
			continue
		dfs(u,v)
		flag=False
	if flag:
		s.add("".join(path))
	#到叶子节点,就要回溯了
	path.pop()
		
n=int(input())
s=""+input()  #方便与点对应
Tree=[[] for i in range(n+1)]
for _ in range(n-1):
	u,v=map(int,input().split())
	Tree[u].append(v)
	Tree[v].append(u)
path=[] #记录路径
s=Set()
dfs(1,0)

print(len(s))


  1. 树的直径
    在这里插入图片描述

  1. 直径的性质
    在这里插入图片描述
def def(u,fa,pre=None):
	global S
	#维护根节点到最深叶子节点的编号
	if d[u]>d[S]:
		S=u
	for v,w,in G[u]:
		if v==fa:
			continue
		d[v]=d[u]+1
		if pre:
			#记录前驱
			pre[v]=u
		dfs(v,u,pre)
n=int(input())
G=[[] for i in range(n+1)]
d=[0]*(n+1)
pre=[0]*(n+1)

for _ in range(n-1):
	u,v,w=map(int,input().split())
	G[u].append([v,w])
	G[v].append([u,w])
S=1
dfs(1,0)
d[S]=0
dfs(S,0,pre)
L=d[S]

# 获取直径所在的边
L_list=set()
while S!=0:
	L_list.add(S)
	S=pre[S]
for u in L_list:
	for i,(v,w) in enumerate(G[u]):
		if v in L_list:
			G[u][i]=[v,w-1]
S=1
dfs(1,0)

  1. 树的重心
  • 定义:最大值取得最小的节点就是 整个树的重心
    在这里插入图片描述

  • 性质

    • 性质1
      在这里插入图片描述

    • 性质2
      在这里插入图片描述

    • 性质3在这里插入图片描述

    • 性质4
      在这里插入图片描述

  • 重心的求解:很明显就是求解一个mss数组,求解一个最大值中取得最小值就是重心

# 求解重心的代码逻辑很简单,就是遍历每一个节点,求解去掉这个节点之后,各个子树的最大值,下方的子树很好比较,就是dfs遍历的过程比较就好,上方的子树的话,统计一下包括当前节点下方字数之和,然后总的减去下方等于上方子树之和
def dfs(u,fa):
	# 维护以当前节点为子树的节点总数量
	sz[u]=1
	for v in G[u]:
		if v==fa:
			continue
		dfs(v,fa)
		sz[u]+=su[v]
		mss[u]=max(mss[u],su[v])
	mss[u]=max(mss[u],n-sz[u])
n=int(input())
G=[[] for i in range(n+1)]
sz=[0]*(n+1)
mss=[0]*(n+1)
for _ in range(n-1):
	u,v=map(int,input().split())
	G[u].append(v)
	G[v].append(u)
dfs(1,0)
root=mss.index(min(mss[1:]))

  1. LCA问题
  • 定义:
    在这里插入图片描述

  • 朴素求解算法
    在这里插入图片描述

    • 祖先一定是节点编号是相同的
  • 优化算法–倍增思想
    在这里插入图片描述
    在这里插入图片描述

    • 倍增的思想,状态定义基本上都是这么进行一个定义的
    • 这个p节点的一维下标表示 当前节点,二维下标表示走的2的i次方步数,二位下标的值表示 节点编号
    • 通过dfs 预先处理出来,预先处理出深度数组,这个p数组处理从小到大到大即可,保证前面处理的状态已经处理出来
def lca(x,y):
	# 保证x更深
	if deep[x]<deep[y]:
		x,y=y,x
	#利用倍增思想往上走
	#从大到小枚举
	for i in range(20,-1,-1):
	# 
		if deep[p[x][i]]>=deep[y]:
			x=p[x][i]
	#此时深度相同 此时具备两种情况,好好分析,不用记忆,现推即可
	if x==y:
		return x
	#一起往上走 #保证是最小公共祖先
	for i in range(20,-1,-1):
		# 只有不等才走,才能保证是最近祖先,只有不等才可以保证是公共
		if p[x][i]!=p[y][i]:
			x,y=p[x][i],p[y]p[i]
	return p[x][0]
	
  • 第一步通过 dfs预处理出来 深度数组
  • 第二步找到 两个点中深度更大的
  • 第三步 让他跳到与另外一个点深度相同
  • 第四步:判断这个点是不是lca,若不是,两个点一起跳(只有不是祖先才可以一起跳,记住这个东西)

  1. 树上差分

  • 差分定义:
    在这里插入图片描述
    • 最直白差分的应用 就是 快速的给一段区间加上或者减去一个值
    • 稍微转变一下思维,差分的应用,还可以统计树上路径某个节点出现的次数区间某个位置查询的次数,都可以用差分进行统计,这个要进行稍微一下转换

  • 树上差分
    在这里插入图片描述
  • 问题引入
    在这里插入图片描述
    在这里插入图片描述
  • 利用dfs回溯的特性,对点的差分只需要记住 对 s,t,lca,root这四个节点进行操作就可以了,这是非常关键的
  • 然后利用回溯的特性,通过一个cnt数组,相当于cnt[u]+=cnt[v]然后就可以统计出每个点染色的次数,
    • 第一遍dfs先预处理出深度和p数组
    • 树上差分就是 关注这四个点cnt[s]++ cnt[t]++ cnt[lca]-- cnt[root]–
    • 注意这个root=p[lac][0]
    • 第二遍dfs就可以进行回溯,统计出cnt数组,也就是每个点染色的次数


  • 边差分
  • 问题引入:
    在这里插入图片描述
    在这里插入图片描述
  • 这个和点差分 就是多了一步将边权塞给这条边所连的深度较深的节点,然后多了一个权值
  • 维护一个val数组,记录权值
  • 维护一个cnt 和点差分一样,记录每个点
  • 最后对于每一次询问,进行暴力处理一下就ok
  1. DFS序要进行学习,2023考到了省赛 异或和
  • DFS序的定义
    在这里插入图片描述 - DFS序的求解在这里插入图片描述

  • DFS序代码实现也非常简单

    • 就是维护一个in 和 out的数组进行维护即可
    • in[node]=++cnt
    • in数组就是节点所对应的dfs序
    • out数组也是这样的类似道理
  • DFS序的性质
    在这里插入图片描述

    • 在以2为根子树中,所有节点都加上一个k,那么根据DFS序的性质,我们就可以用树状数组或者线段树进行维护,根据这个dfs序
    • 用线性的数据结构,去维护这个区间,单点修改,区间查询
    • 线段树的下标 是 dfs序,里面的值可以根据具体情况来做修改
  • 例题讲解


  1. 述链剖分 基于DFS序的第一个性质
  • 定义:
    在这里插入图片描述
  • 重链剖分
    在这里插入图片描述
    在这里插入图片描述
    • 这个图像就非常形象,重链剖分

9. 并查集

  1. 并查集注意是根节点 进行合并,这样才能保证是高效的 路径压缩就是比避免所消耗的时间复杂度过高
    在这里插入图片描述
    • 每次合并都是根节点的合并
    • 直接建立并查集是比较简单的应用
    • 根据图建立并查集是需要进行稍微转换一下的思路

  1. 需要稍微转换一下的并查集星球大战
# 删除点等价于删除该点的所有边
# 删除边求连通块和添加边求连通块是等价的操作
# 但是删除边的话,图的结构就会发生变化,所以我们从添加边入手
# 就是模拟图上的删边操作,如何进行逻辑上的删除边
# 这道题目的关键就i是 模拟图上的删除边的操作,理解了这一点,就非常容易理解下面的操作

  1. 可撤销并查集 (有什么任务驱动,为什么引入可撤销)
  • 启发式合并操作,就是维护一个统计子树
    在这里插入图片描述

  1. 可撤销并查集举例
    在这里插入图片描述

  • 分析过程:
    在这里插入图片描述
    在这里插入图片描述

10. 图论

  1. 图的dfs遍历
    在这里插入图片描述

  1. dfs遍历模板
    在这里插入图片描述

  1. BFS遍历
    在这里插入图片描述

#标准的bfs模板
  1. 拓扑排序
  • 定义:
    在这里插入图片描述

在这里插入图片描述

  • 如果想要拓扑排序是唯一的,按照字典序的顺序输出,那么就是稍微修改一下代码就好,转换成为优先队列即可,每次从队列中取出的是字典序最小的元素即可
  • BFS实现拓扑排序 区别一般在于
    在这里插入图片描述
# 拓扑排序模板题目 拓扑排序针对的是,有向无环图
# 首先找到入度为0的点,将入度为0的点都加入deque中 初始化
# 什么元素出队,队首元素出队
def top():
	q=deque()
	for i in range(1,n+1):
		if rudu[i]==0:
			q.append(i)
	#初始化完成
	ans=[]  # 记录拓扑排序
	while len(q):
		#队首元素出队
		u=q.popleft()
		# 有的时候,出队就要判断是否到大了终止条件
		# 扩展
		for v in G[u]:
			ru[v]-=1
			# 如果度数为0的话
			# 直接进行入队操作
			if ru[v]==0:
				q.append(v)
	#终止条件判断
	if len(ans)!=n:
		print(-1)
	else:
		print(*ans,sep=" ")
		


  1. 最短路floyed算法
  • 定义:
    在这里插入图片描述
    在这里插入图片描述
  • 初始化操作需要进行一个相关的注意
    • dp[i][j]=INF
    • 如果无边,等于INF,右边等于边权
    • dp[i][i]=0
n,m,q=map(int,input().split())
INF=int(1e9)
#初始化为无穷
dp=[[INF]*(n+1) for i in range(n+1)]
for i in range(1,n+1):
    dp[i][i]=0

for i in range(1,m+1):
    u,v,w=map(int,input().split())
    # 避免出现 重边,选取最小的
    dp[u][v]=dp[v][u]=min(dp[u][v],w)

# floyed算法模板
for k in range(1,n+1):
    for i in range(1,n+1):
        for j in range(1,n+1):
            # 就是以k作为中间点,看是否能进行一个更新
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])

# 题目要求 不能到大救赎出-1
# 不能到大 说明 dp[i][j]=INF
for i in range(1,n+1):
    for j in range(1,n+1):
        if dp[i][j]==INF:
            dp[i][j]=-1

for _ in range(q):
    s,t=map(int,input().split())
    print(dp[s][t])

# 分析题目:要想获得利润最大
# 对每一个城市都要去最大利润
# 每一个城市 可以在自己城市卖,也可以送往其他城市进行出售
# 所以可以定义每一件的利润 g_ij=sj-ai-f_ij

# 其中f_ij 表示i到j的最短距离
# 所以可以使用floyed算法 进行求解

n,m=map(int,input().split())
INF=int(1e8)
dp=[[INF]*(n+1) for i in range(n+1)]
for i in range(1,n+1):
    dp[i][i]=0

for i in range(m):
    u,v,w=map(int,input().split())
    dp[u][v]=dp[v][u]=min(dp[u][v],w)

# 初始化完成
a=[0]*(n+1)
p=[0]*(n+1)
s=[0]*(n+1)
a[1:]=list(map(int,input().split()))
p[1:]=list(map(int,input().split()))
s[1:]=list(map(int,input().split()))

g=[[0]*(n+1) for i in range(n+1)]

# 开始进行floyed模板
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][j],dp[i][k]+dp[k][j])


#进行构造利润数组
for i in range(1,n+1):
    for j in range(1,n+1):
        g[i][j]=s[j]-s[i]-dp[i][j]

# 构造好了之后,开始求出最大利润
total_lirun=0
for i in range(1,n+1):
    maxs=0
    for j in range(1,n+1):
        if maxs<g[i][j]:
            maxs=g[i][j]
    total_lirun+=maxs
print(total_lirun)


  1. 最短路dijstra 对比理解
  • 定义:
    在这里插入图片描述
    • 和普通的bfs不同,这里的标记的时候,是出队的时候进行标记 因为第一次出队的才是距离起点最短的距离,第一次入队并不一定是
    • 还有的就是 重要的操作就是 每次出队的时候 是选择 距离最短的点进行 出队操作,所以不是普通的队列,而是优先队列
    • 还有一个操作就是 松弛操作
    • 理解了这三点就非常容易理解dijstra算法了:处理非负边权
      在这里插入图片描述
      在这里插入图片描述


from queue import PriorityQueue

def dijstra(s):
    #从起点s出发到各个点的最短距离
    inf=int(1e8)
    dp=[inf]*(n+1)

    # vis[i]表示点是否出队列
    vis=[0]*(n+1)
    #q表示优先队列 每次出来的是距离最短的点
    #所以传入的是一个二元组,距离+点
    q=PriorityQueue()
    dp[s]=0
    q.put([dp[s],s])
    while not q.empty():
        #出队
        dis,u=q.get()
        #第一次出队的进行标记,这个和普通的dfs不同
        vis[u]=1
        #bfs的扩展部分 编程用u进行松弛操作
        for v,w in G[u]:
            if dp[v]>dp[u]+w:
                dp[v]=dp[u]+w
                q.put(dp[v],v)
    for i in range(n+1):
        if dp[i]==inf:
            dp[i]=-1
    print(*dp[1:],seq=" ")

    #初始化起点的距离为0
    dp[0]=0
    q.put(0)



n,m=map(int,input().split())
G=[[] for i in range(n+1)]
for _ in range(m):
    # 单相边
    u,v,w=map(int,input().split())
    G[u].append([v,w])

dijstra(1)



  1. 最短路bellman-Ford算法
    在这里插入图片描述
  • 不断利用边进行松弛
  • 松弛n-1轮松弛即可求出单源最短路
  • 还能用于判断负权环


# 和dijstra一样也要进行松弛操作
# 松弛操作很好理解 dp[v]=dp[u]+w

import sys
input=sys.stdin.readline

n,m=map(int,input().split())
c=[0]+list(map(int,input().split()))
# 存储图的第三中方式,直接用边存储 还有邻接表,邻接矩阵
e=[]
for _ in range(m):
    u,v,w=map(int,input().split())
    e.append([u,v,w])
    e.append([v,u,w])

inf=int(1e10)
# 初始化
dp=[inf]*(n+1)
dp[1]=0

# 经过n-1次 循环
for i in range(n-1):
    #每次循环都要遍历所有的边
    for u,v,w in e:
        if v!=n:
            res=c[v]
        else:
            res=0
        if dp[v]>dp[u]+w+res:
            dp[v]=dp[u]+w+res

print(dp[n])

# 如果要进行判断负权环,也很好进行一个求解过程
# 直接松弛n次,如果din次还能松弛,说明存在负权换
# 通过一个标记变量进行记录

  1. 最小生成树
  • 定义:
    在这里插入图片描述
  • 具体操作
    在这里插入图片描述


# 最小生成树 kruskal算法
# 边排序+并查集(并查集就是合并两个集合,就是连边的操作)

n,m=map(int,input().split())
e=[]

for _ in range(m):
    u,v,w=map(int,input().split())
    e.append((w,u,v))

# 边排序 根据权值从小到大进行一个排序
e.sort()

# 并查集
p=list(range(n+1))

def find_root(x):
    if x==p[x]:
        return p[x]
    p[x]=find_root(p[x])
    return p[x]

def merge(a,b):
    root_a=find_root(a)
    root_b=find_root(b)
    if root_a!=root_b:
        # 进行连接的时候,是根节点之间进行连接
        p[root_a]=root_b

sums,max_val=0,0
# 从小到大枚举所有边,进行合并
for w,u,v in e:
    root_u=find_root(u)
    root_v=find_root(v)
    if root_u!=root_v:
        p[root_u]=root_v
        sums+=1
        max_val=max(max_val,w)
print(sums,max_val)
  1. Prim算法
    在这里插入图片描述
  • 距离集合最小的点在这里插入图片描述

  • 算法步骤:
    在这里插入图片描述


# prim 算法是不断加点的过程,
# kru 算法是 不断加边的过程
# prim 算法又和 dijstra算法 有点不一样
# prim算法 每次找到离集合最近的点,dijstra算法每次找到 离起点最近的点

inf=int(1e9)
n,m=map(int,input().split())
maps=[[inf]*(n+1) for i in range(n+1)]

for _ in range(m):
    u,v,w=map(int,input().split())
    maps[u][v]=maps[v][u]=min(maps[u][v],w)

# 这歌和floyed算法 有一些区别,floyed算法初始化还要 dp[i][i]=0


d=[inf]*(n+1)
max_val=0
# 初始化离集合最近的点是从 1号点开始
u=1
# 进行n-1次 循环 
for i in range(n-1):
    # 将这个离集合最近的点加入集合中
    d[u]=0
    next_u,next_val=0,inf
    #利用这个离集合最近的点去更新其他点到集合的距离 并且找到下一个离集合最近的点
    for v in range(1,n+1):
        if d[v]==0:
            continue # 不能选择已经在集合中的点
        d[v]=min(d[v],map[u][v])
        #找到 离集合更近的点
        if d[v]<next_val:
            next_val=d[v]
            next_u=v
    u=next_u
    max_val=max(max_val,next_val)
    
print(n-1,max_val)


11.树状数组

  1. 前置知识lowbit操作
    在这里插入

  1. tree[i] 树状数组的含义:
    在这里插入图片描述
  • tree[x]所覆盖的区间长度就是lowbit(x)
  • tree[x]节点的父亲节点为tree[x+lowbit(x)]
  • 明白树状数组的定义方式
    在这里插入图片描述
  • 求取前缀和 就是不断的进行区间拆分操作,将一个大的区间拆分成为小的区间之和,通过x-lowbit(x)核心操作实现
    在这里插入图片描述
  • 区间查询+单点修改


def lowbit(x):
    return x&(-x)


# 区间查询,单点修改
# 进行区间查询非常简单,就是将一个大区间拆解成为若干个小的区间
# 通过不断x-lowbit(x)实现
def query(x,tree):
    ans=0
    #求前缀和,进行区间拆分
    while x:
        ans+=tree[x]
        x-=lowbit(x)
    return ans

# 进行单点修改的话,
## 首先要知道哪些区间包含了 这个单点,对单点进行修改,对应的区间也会发生变化

# 这个单点进行修改,需要找到哪些区间包含这些单点
# 如果修改为y 那么tree+=(y-tree[x]) 必须是这种增量形式
def add(x,y,tree,n):
    while x<=n:
        tree[x]+=y
        # 找到父亲节点,父亲节点必然包含这个单点
        x+=lowbit(x)

n=int(input())
a=[0]+list(map(int,input().split()))
tree=[0]*(n+1)

# 初始化树状数组
for i in range(1,n+1):
    #这个初始化方式和差分数组的初始化方式有点类似
    add(i,a[i],tree,n)

# m次操作
m=int(input())
for  i in range(m):
    op,a,b=map(int,input().split())
    if op==1:
        add(a,b,tree,n)
    else:


  • 区间修改+单点查询
    在这里插入图片描述
  • 进行一个转换,又可以变成区间查询+单点修改
# 区间修改,单点查询
# 整体流程,可以转换成为 区间查询+单点修改

def lowbit(x):
    return x&(-x)

def query(x,tree):
    ans=0
    while x:
        #拆分区间的操作
        ans+=tree[x]
        x-=lowbit(x)
    return ans

def add(x,y,tree,n):
    # 单点修改
    while x:
        tree[x]+=y
        x+=lowbit(x)
        


n=int(input())
a=[0]+list(map(int,input().split()))
tree=[0]*(n+1)

for i in range(1,n+1):
    add(i,a[i],tree,n)
chafen=[0]*(n+1)
for i in range(1,n+1):
    chafen[i]=a[i]-a[i-1]
# 构造完差分数组,之后
# 其余操作完全一样,区间修改,转换成两点修改

# 单点查询 转换成为 前缀和操作
# 看到情况1:发现是单点修改
# 看到情况2:发现经过对题目的式子进行一个转换 是一个区间查询

# 那么就是标准的树状数组

def lowbit(x):
  return x&(-x)

def query(x):
  ans=0
  while x:
    ans+=tree[x]
    x-=lowbit(x)
  return ans

def add(x,y):
  while x<=n:
    tree[x]+=y
    x+=lowbit(x)

n,m=map(int,input().split())
a=[0]+list(map(int,input().split()))
tree=[0]*(n+1)


for i in range(1,n+1):
  add(i,a[i])
for i in range(m):
  op=list(map(int,input().split()))
  if op[0]==1:
    x,z=op[1],op[2]
    add(x,z-a[x])
    a[x]=z
  else:
    i=op[1]
    ans=(2*i-n-2)*a[i]+query(n)-2*query(i-1)
    print(ans)
  • 总结 前缀区间维护最大值,异或和都可以,但是对于复杂的区间问题,树状数组就有点难搞了,这个时候引入线段树
    在这里插入图片描述

  1. 二维树状数组
    在这里插入图片描述
# 单点修改
def add(x,y):
    while x<=n:
        tree[x]+=y
        x+=lowbit(x)

def add(x,y,z):
    i=x
    while i<=n:
        j=y
        while j<+m:
            tree[i][j]+=z
            j+=lowbit(j)
        i+=lowbit(i)

# 对比区间查询
def query(x):
    ans=0
    while x:
        ans+=tree[x]
        x-=lowbit(x)
    return ans

# 求取矩阵 (1,1)- (x,y)之和
def query(x,y):
    ans=0
    i=x
    while i>0:
        j=y
        while j>0:
            ans+=tree[i][j]
            j-=lowbit(j)
        i-=lowbit(i)
    return ans

  • 一维求取区间和二维求子矩阵和 (前缀和是离线的操作,树状数组是动态的操作)
    在这里插入图片描述

  1. 树状数组的应用:不仅仅进行前缀和这个简单的操作
    在这里插入图片描述
def descrete(a):
    b=list(set(a))
    b.sort()
    ans=[]
    for i in range(len(a)):
        ans.append(bisect_left(b,a[i])+1) # 离散化后从1开始
    return ans

def lowbit(x):
    return x&(-x)

def add(x,y):
    while x<=n:
        tree[x]+=y
        x+=lowbit(x)
def query(x):
    res=0
    while x:
        res+=tree[x]
        x-=lowbit(x)
    return res

n=int(input())
a=list(map(int,input().split()))
a=descrete((a))
a=[0]+a
ans=0
tree=[0]*(n+1)
for j in range(1,n+1):
    #将a[j]放入树状数组,此处的a[j]下标
    add(a[j],1)
    ans+=j-query(a[j])
print(ans)

12. 离散化操作

  1. 离散化:不关注数字本身,只关注大小关系时,利用排名替代原始数据
    在这里插入图片描述
  2. 数组离散化的步骤:
  • 把a拷贝一份设置为b
  • 对b排序,去重
  • 将a中每个元素设置为b数组中的下标(二分查找)
  1. 一般不会单独考察离散化操作,一般会结合树状数组,线段树等结构一起考察

  1. 代码如下:
from bisect import bisect_left
def discrete(a):
	b=list(set(a))
	b.sort()
	ans=[]
	for x in a:
		ans.append(bisect_left(b,x))
	return ans

	#第二种方法,通过一个字典进行相关操作
	# 两种方法都可以 进行一个离散映射过程
	values=list(range(len(b)))
	dicts=dict(zip(b,values))
	ans=[]
	for x in a:
		ans.append(dicts[x])
	return ans
	


13. 构造

  1. 构造问题常见的题型
    在这里插入图片描述
    • 数学问题:
    • 图论问题:这个算是比较有难度的构造方法
    • 字符串处理:这个需要发现规律,并且需要进行一个相关方面的一个总结过程

  1. 构造的应用题型
    在这里插入图片描述

  1. 经典例题解析
    在这里插入图片描述
    在这里插入图片描述
  • 解析如下:真数学题
    在这里插入图片描述

  1. 经典例题解析:
    在这里插入图片描述
    在这里插入图片描述
  • 通过这个 案例初步分析 2 5 7 公差满足的性质 一定小于等于 最小的间隔,从最小的间隔开始遍历,看是否满足条件(就是写一个check函数,看从最小值出发,生成的序列,一定要包含这N个数,);如果满足条件,那么就可以直接跳出循环,因为询问的是 最小项数
  • 正解这个答案,真的感觉并不是那么容易进行一个想得出来的,还是非常难,转换成 gcd构造,真的不容易思考出来

  1. 经典例题分析:图论上的构造问题
    在这里插入图片描述
  • 通过分析题目,可以初步得到
  1. 经典例题解析
    在这里插入图片描述

  1. 经典例题解析
    在这里插入图片描述
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值