笔记1
sort函数:把数组从小到大排序
max函数:求出两个数的最大值
min函数:求出两个数的最小值
unique函数:使用前提是先排好序,再使用,效果是去重
merge_sort归并排序
reverse函数:翻转数组
random_shuffle函数:把a[1]到a[n]随机打乱
swap函数:交换两个数
没有单调性不能二分
位运算运行速度比加减乘除取模运算速度快
位运算符号有:& ^ |
只有两个矩阵的行和列一样时,才能进行矩阵的加减法。只有一个矩阵的行等于另一个矩阵的列时,才能进行乘法
矩阵乘法有结合律,但是没有交换律
搜索问题分为:
-
最优解问题
-
可行解问题
-
解数量问题
必须是最优解问题,每一步的代价都是最小步数,才能用bfs,其余问题都用dfs
优化技巧:
-
剪枝:少搜一些无用状态,分为可行性剪枝和最优性剪枝
-
卡时
-
随机顺序搜索
全局变量在堆空间,局部变量在栈空间
使用long long类型的变量,可以把浮点数强制转化成整数
堆空间为256M,可以存6.4×10的7次方个int,栈空间为64KB,只能存16384个int
堆默认定义为大根堆,栈是先进后出(一个桶),队列是先进先出:FIFO(first in first out)
单调队列要用手写队列,用单调递增队列来求最小值,用单调递减队列来求最大值。
归并排序核心思想就是分治,分完再治。
顺序赋值比随机顺序赋值更快。
从起点和重点同时开始bfs直到在中间某个位置相遇叫双向bfs
由点与边构成的元素分类:有向图,无向图
度:度分为入度,出度,只会出现在有向图,无向图只有度这个概念。
入度:指向这个点的边的总数,顶点v的度是指与顶点v相连的边的数目
出度:从这个点出去的点的总数,顶点v的度数为以顶点v为起点的边的数目和
度(无向图):链接这个点的总数,在无向图中,顶点v的度是指与顶点v相连的边的数目。
自环:自己连接自己,也就是一条边的起点和终点为同一个点
路径:从一个点到另一个点的过程所经过的边
简单路径:不能走重复点和边的路径
环:终点 = 起点的路径
简单环:简单路径和环的集合体,保证起点 =终点且除起点(终点)外不能经过重复的点和边的路径。
连通:两个点能够通过路径到达
连通图:一个图所有的边都能互相到达
特殊类型的图:树(无向 无环 联通)
森林(无向 无环)
有向树 (有向 无环)
外向树(所有边都朝外)
内向树 (所有边都指向一个点)
章鱼图/基环树(有环且只有一个环, 中间的点可以延伸出一棵树)
仙人掌(有多个环)
(边)仙人掌:无向联通且有多个环的图(但要满足任何一条边都只在一个环中)
仙人掌图:
-
边仙人掌
-
点仙人掌
DAG(有向无环图)
注:链式结构既是外向,又是内向
图的基本要素
- 点和边(树是有点和有边的一种结构)。
二分图匹配问题:
- 在二分图中找到尽量多的点,使得能够互相匹配,但是每个点只能用一次。
二分图一定是无向图。
如果把章鱼的环删掉一条边,就可以变成一个树
树任意加上一条边就是章鱼
染色法:把一个点染色为 1,然后一层一层向外染色。如果一条边连接的两个点的颜色一样,就说明不是二分图,否则就是。
拓扑排序:针对 DAG。
对有向图中的每一个点进行排序,使得最后的排序结果都是从左向右指的。
不断删除入度为0的点,然后不断把入度为0的点加入答案。
那如何删除一个点呢?其实不需要删除,只需要将入度减掉即可。
最短路问题可以分为两类:
-
单源最短路问题
-
多源最短路问题
单源最短路问题:一个起点到其他点最短路
多源最短路问题:多个起点到其他点最短路
最短路的三角不等式:dist[i][j]<=dist[i][k]+dist[k][j]
解决多源最短路的算法:flogd:dist[i][j][k],意思是从j走到k,中间经过很多点,中间节点的编号必须都<=i
所有的动态规划(DP)题中,都包含状态,转移方程,初始化条件
从一张 m 条边的图中找 n−1 条边,使得找出来的边和已有的点构成一棵树,组成的图就叫做原图的生成树。一个生成树的大小是选出来的所有边的边权之和。大小最小的生成树被称为 最小生成树。
单源最短路:求一个起点到其他点的最短路,起点是固定的。
多源最短路:求多个起点到其他点的最短路,起点不固定。
强连通分量定义在有向图,找到点边使互相能走到就构成了一个强连通分量,独立的点也可以构成一个强连通分量,找强连通分量要找环
连到自己祖先的边叫回边,反之叫做横叉边,横叉边虽然不能组成环,但能扩大一个环
DP有:树形DP,背包DP,区间DP,排列DP,数位DP,状压DP,插头DP,博弈DP,不能归类到其他DP的DP叫一般DP
分治算法的核心思想就是“分而治之”。
大概的流程可以分为三步:分解 -> 解决 -> 合并。
-
分解原问题为结构相同的子问题。
-
分解到某个容易求解的边界之后,进行递归求解。
-
将子问题的解合并成原问题的解。
分治法能解决的问题一般有如下特征:
-
该问题的规模缩小到一定的程度就可以容易地解决。
-
该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质,利用该问题分解出的子问题的解可以合并为该问题的解。
-
该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
二叉搜索树具有以下性质:
-
这是一颗二叉树
-
每个节点的左儿子比自己小
-
每个节点的右儿子比自己大
二叉搜索树又该如何建立呢?
-
将节点从根节点开始插入
-
与当前节点比较大小
-
如果比当前节点储存的值大,向右递归
-
如果比当前节点储存的值小,向左递归
注意:
如果我们从小到大插入二叉搜索树,就会出现一条链的情况。
那么如何避免?
-
尽可能随机地将元素插入二叉搜索树中;
-
当我们发现一个二叉搜索树的高度很接近于元素的数量时,我们就将其进行局部推倒重建;
-
选取值为中间的一个元素为根,再次进行插入操作;
-
平衡树的做法(不属于 NOIP 的考纲)。
二叉堆是一种特殊的二叉树,满足任意上面节点的值都比下面节点大的叫做大根堆,满足任意上面节点的值都比下面节点小的叫做小根堆。
一般二叉堆能解决什么问题呢?
-
在O(log n)的时间内插入一个元素
-
在O(log n)的时间内删除一个元素
-
在O(1)的时间内查询最大值
二叉堆插入操作如何实现呢?(以大根堆为例)
从根节点开始,插入一个值,如果当前值比根节点大,与根节点交换存储的值,然后往子树大小更小的儿子递归,直到某个儿子大小为0(即没有某一边儿子),新建一个节点来储存这个值,这样我们可以保证插入操作一定不超过log n次递归。
二叉堆删除操作如何实现呢?(以大根堆为例)
将当前节点权值视为0,与最大的儿子交换权值并递归,直到节点是一个叶子(无左右儿子),然后删除该叶子。,这样我们可以保证删除操作一定不超过log n次递归。删除根节点即可弹出最大值。
二叉堆查询最大值操作如何实现呢?(以大根堆为例)
取根节点的权值即可。
一般实战中二叉堆怎么写呢?
使用STL中的priority_queue,对于删除操作,记录哪些元素被删除了,如果堆顶是这个元素那就再多弹出一次即可。
线段树解决什么问题呢?
各种各样的序列操作问题,例如最常见的问题:
有一个长度为N的序列(可能有初始值),然后有Q次操作,每次操作可能是以下两种之一:(1)修改一个位置的值 (2)查询一个区间的权值和 1≤N,Q≤10^5
如何构建线段树?首先令根节点的范围为[1,N],然后递归建立左右子节点。注意一共需要2N个节点来建立线段树。
线段树是一种二叉树结构,线段树上每个节点对应一个区间[L,R],节点的左儿子对应[L,Mid],右儿子对应[Mid+1,R]。
线段树单点修改
从根开始向下递归,找到包含这个点的所有区间,然后修改对应区间的统计值。
线段树区间查询
查询的时候将区间作为参数传入
如果查询区间是当前区间的,那么返回当前节点的统计值,如果查询区间是当前区间的一个子区间,根据将查询区间按情况递归进入左儿子,或者右儿子或者两边。这样可以把一个查询区间拆成已有的不超过2log n个区间。
线段树是一种解决区间修改与区间查询的数据结构,树状数组是一种简单高效的解决单点修改与前缀查询的数据结构,也就是说,树状数组能实现的功能,线段树都可以,但是线段树通常比树状数组慢一倍时间,代码长三倍。
RMQ问题,又称区间最值问题
ST表的作用:在O(nlogn)的时间内预处理完后,可在O(1)的时间查询区间最值。
碰到题目不会做,先写个模拟压压惊。
枚举的思想是不断地猜测,从所有可能的集合中一一尝试,然后再判断是否符合题目的条件。
单独提到枚举时我们往往认为这是一个暴力做法,但事实上并非如此,恰当的枚举往往会是解题的关键步骤。
模拟就是用计算机来模拟题目中要求的操作。
模拟题目通常具有码量大、操作多、思路繁复的特点。由于它码量大,经常会出现难以查错的情况,如果在考试中写错是相当浪费时间的。
做模拟题的步骤:
-
先看懂题意,过一下样例;
-
在动手写代码之前,在草纸上尽可能地写好要实现的流程;
-
在代码中,尽量把每个部分模块化,写成函数等;
-
对于一些可能重复用到的概念,可以统一转化,方便处理;
-
调试时分块调试。模块化的好处就是可以方便的单独调某一部分;
-
写代码的时候一定要思路清晰,不要想到什么写什么,要按照落在纸上的步骤写。
二分定义:在一个单调的有限数列上快速查找某一特定值的方法。
深度优先搜索。DFS 顾名思义就是在搜索树中优先搜索向深处延伸。简单来说就是“有路则走,无路回头”,总结为一个字:“莽”!
树是什么?
-
N个点,N-1条边的连通图。或者说,没有环的连通图。
-
有根树,无根树
-
树根,树上节点的父亲、儿子、祖先、后代、兄弟
-
一个节点的深度定义为到根节点的距离
如何存储一棵树?
使用Vector[i]存储与节点i相邻的点的编号(稀疏图的存储方式)
如何遍历一棵树?
从树根开始,DFS递归遍历,每次遍历记录父亲是谁,避免死循环。
LCA问题的解法:
-
通过欧拉遍历序(ETT)转化为区间最值(RMQ)问题。
-
树上倍增法
如何生成一棵树的欧拉遍历序?
从根节点开始遍历,每次到达或者返回一个节点,将这个节点的编号放入序列末尾。
并查集实际维护的内容,可以看做是对集合的维护,且支持合并集合。
并查集的维护思路是,将每个集合做成一颗树型,用树的根作为集合的标志。那么,判断两点是否在一个集合内,只要找到两点所在树的树根,判断是否相同即可。合并两个集合时,让一颗树的树根父亲设为另一颗树的树根即可。用这样的结构即可维护点与点间连通性判定,但是,显然最坏这样复杂度会达到O(n^2)。
动态规划组成:有限状态,状态值函数,状态转移方程
状态就是能够恰好表达当前需要的信息的一组数据。
动规和搜索都是求解状态值函数的方法,一个递推、一个递归,一个重复值只算一次,一个重复值可能算多次,只要能搜就能动规,只要能动规也能搜索
通常,我们通过拓扑序遍历所有状态来逐个计算状态值函数。
状态转移方程需要满足如下条件:
-
除了初始状态以外,每个状态都可以通过其他状态计算得出
-
依赖关系不能成环(成环了,该按照什么顺序算???)
搜索的时间复杂度是指数界别的,而记忆化搜索比较快(可能快于动态规划)。
区间DP一般解决一些区间上的问题,往往和序列DP较难区分,如果一道题序列DP无法处理,可以考虑区间DP。通常dp[i][j] 表示区间[i,j]的最优值是多少,然后根据枚举分界点来转移。
环套树DP,通过断掉一条边变成普通树上DP
区间DP特点:n一般都很小,一般只有50-2000。
树形DP分类:
-
O(n)的树上递推(DFS)
-
以子树为单位的转移(即dp[i]表示子树i的最优解)
cout换行时一定要用"\n",不要用endl,因为endl比"\n"要慢5-6倍。
当求多源最短路时,用 Floyd;当求单元最短路且有负数权值时,用 SPFA;当求单源最短路1且无负数边权时,用 Dijkstra 带堆优化的版本。
边上路径和最少的生成树称为最小生成树。
如何判断一个数是不是生成树?
看这个数连不连通。
强联通分量:
-
解释:有向图中,每个点之间都能互相到达的的子图。
-
本质:在有向图上找环。
次小生成树:
-
第二小的生成树。
-
删掉一条边,再加上一条边,使得差值尽量小,并且要是一个树。
-
如果一条边在最小生成树上,我们就叫他树边,如果不在最小生成树上就叫他非树边。
-
删掉一条树边,加上一条非树边。
-
倍增 LCA 询问环上最大的值(章鱼图)。
SPFA本质上是 Bellman_ford 的优化。
- 简要思路
维护一个队列,表示可能改变其他点最短路的点。不断向队列中加入可能改变其他点的最短路,然后再把新的点加入队列中,直至队列为空。
单源最短路Dijkstra
限制:边的权值必须都是正数。
- 简要思路
每次选取 dist 值最小的值,也就是选取已经求出最短路的点(因为边的权值都是正数,所以它当前是最小值,那后面也不会有),然后对其进行松弛操作(用自己的最短路去更新其他点的最短路)。
判断二分图:
- 简要思路
染色法:把一个未染色的点染色为1,然后一层一层向外染色。如果一条边连接的两个点的颜色一样,就说明不是二分图。注意要不断地循环找未染色的点,因为不保证连通。
二分图:
-
把无向图的所有的点分为两个部分,第一部分的点连接的一定是第二部分的点,第二部分的点连接的一定是第一部分的点。也就是说一条边一定是连接第一部分和第二部分的点。不要求连通。
-
树和森林就是二分图。在树中,深度为奇数的为第一部分,深度为偶数的为第二部分。
-
存在奇环(奇数的环)的不是二分图,没有奇环的就是二分图。同样,所有的二分图也一定没有奇环。
二叉搜索树基本实现
点击查看代码
const int L=0,R=1;//L 左儿子,R 右儿子
struct node{//一个树上节点
int son[2];//记录两个儿子的下标,分别对应于 son[L],son[R]
int val;//这个节点储存的值
}a[MAXN];
int cnt;//用于分配节点编号
滚动数组代码
点击查看代码
//滚动数组代码
//时间复杂度:O(nm)
#include<bits/stdc++.h>
using namespace std;
int f[maxn][maxn],v[maxn],w[maxn],m,n;//f[i][j] 代表前 i 个物品已经考虑完,用掉了 j 的体积所能获得的最大价值
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];//读入体积和价值
}
for(int i=0;i<=n;i++)
{
int p=(i&1);//p 代表 i 的奇偶性
int q=p^1;//q 代表 i+1 的奇偶性
memset(f[q],0,sizeof(f[q]));
for(int j=0;j<=m;j++)
{
f[q][j]=max(f[q][j],f[p][j]);//不选
f[q][h+v[i+1]]=max(f[q][h+v[i+1]],f[p][j]+w[i+1]);
}
}
int ans=0;
for(int i=0;i<=m;i++)
{
ans=max(ans,f[n&1][i]);
}
cout<<ans<<endl;//cout<<f[n][m]<<endl;
}
有限背包DP代码
点击查看代码
//有限背包DP代码
//时间复杂度:O(nm^2)
#include<bits/stdc++.h>
using namespace std;
int f[maxn][maxn],v[maxn],w[maxn],z[maxn],m,n;//f[i][j] 代表前 i 个物品已经考虑完,用掉了 j 的体积所能获得的最大价值
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i]>>z[i];//读入体积和、价值和个数
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)//要求状态f[i][j] 用别人去求自己
{
for(int k=0;k*v[i]<=j&&k<]z[i];k++)//考虑第 i 种物品选 k 个
{
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
int ans=0;
for(int i=0;i<=m;i++)
{
ans=max(ans,f[n][i]);
}
cout<<ans<<endl;//cout<<f[n][m]<<endl;
}
区间DP代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int f[maxn][maxn],n,a[maxn],sum[maxn];
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++)
{
f[i][i]=0;
}
for(int len=1;len<=n;len++)//当前要处理长度为 len 的区间
{
for(int l=1,r=len;r<=n;l++,r++)
{
for(int k=l;k<r;k++)
{
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);
}
}
}
cout<<f[1][n]<<endl;
}
树形DP代码
点击查看代码
//树形DP代码
#include <bits/stdc++.h>
using namespace std;
int n,f[maxn],z[maxn];//f[i]代表以i为根的子树的大小
void dfs(int p,int f)//代表当前要dp p点的值p的父亲为f
{
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];
if(q!=f)
{
dfs(q,p);//dp儿子节点的值
}
}
f[p]=1;
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];//从p到q的边
if(q!=f)
{
f[p]+=f[q];
}
}
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int s,e;
cin>>s>>e;
add_edge(s,e);
add_edge(e,s);
}
dfs(1,0);
return 0;
}
求树上路径总长度和
点击查看代码
//求树上路径总长度和代码
#include <bits/stdc++.h>
using namespace std;
int n,f[maxn],ans,z[maxn];//f[i]代表以i为根的子树的大小
void dfs(int p,int f)//代表当前要dp p点的值p的父亲为f
{
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];
if(q!=f)
{
dfs(q,p);//dp儿子节点的值
}
}
f[p]=1;
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];//从p到q的边
if(q!=f)
{
f[p]+=f[q];
}
}
ans+=2*f[p]*(n-f[p]);
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int s,e;
cin>>s>>e;
add_edge(s,e);
add_edge(e,s);
}
dfs(1,0);
return 0;
}
求树的直径代码
点击查看代码
//求树的直径代码
#include <bits/stdc++.h>
using namespace std;
int n,f[maxn][2],z[maxn];//f[i][1]代表以i为根向下最长是多少, f[i][2]代表以i为根向下次长是多少
void dfs(int p,int f)//代表当前要dp p点的值p的父亲为f
{
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i].first;
if(q!=f)
{
dfs(q,p);//dp儿子节点的值
}
}
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i].first;
int d=z[p][i].second;
if(q!=f)
{
int x=d+f[q][0];
if(x>f[p][0])
{
f[p][1]=f[p][0];
f[p][0]=x;
}
else if(x>f[p][1])
{
f[p][1]=x;
}
}
}
f[p]=1;
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];//从p到q的边
if(q!=f)
{
f[p]+=f[q];
}
}
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int s,e,d;
cin>>s>>e>>d;
add_edge(s,e,d);
add_edge(e,s,d);
}
dfs(1,0);
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,f[i][0]+f[i][1]);
}
cout<<ans;
return 0;
}
询问树的最大独立集代码
点击查看代码
//询问树的最大独立集代码
//翻译:在树上选出尽可能多的点,使得点与点之间都互不相邻,问最多选多少个点。
#include<bits/stdc++.h>
using namespace std;
int n,s,e,f[maxn][2];//f[i][0] 代表以 i 为根的子树 i 没选的情况下最多选几个点,f[i][1] 代表以 i 为根的子树 i 选了的情况下最多选几个点
void dfs(int p,int f)//当前要 DP p点的值,其中 p 的父亲为 f
{
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];
if(q!=f)dfs(q,p);
}
f[p][0]=1;
f[p][1]=1;
for(int i=0;i<z[p].size();i++)
{
int q=z[p][i];//从 p 到 q 的边
if(q!=f)
{
f[p][1]+=f[q][0];
f[p][0]+=max(f[q][1],f[q][0]);
}
}
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int s,e,d;
add_edge(s,e,d);
add_edge(e,s,d);
}
dfs(1,0);
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,f[i][0]+f[i][1])
}
cout<<ans<<endl;
}
数位DP代码 求l~r中一共有多少个数
点击查看代码
//数位DP代码 求l~r中一共有多少个数
#include <bits/stdc++.h>
using namespace std;
int l,r,f[30][2],x[maxn];//f[i][j]代表已经把yn~yi填好 j=0代表yn~yi<xn~xi否则等于的情况下
int get(int x_)//求0~x_有多少个数
{
int n=0;
while(x_!=0)
{
n++;
x[n]=x_%10;
x_=x_/10;
}
memset(f,0,sizeof(f));
f[n+1][1]=1;
for(int i=n;i>=1;i--)//当前要填yi
{
for(int j=0;j<2;j++)
{
int up=9;
if(j==1)
{
up=x[i];
}
for(int k=0;k<=up;k++)
{
f[i][(j==1)&&(k==up)]+=f[i+1][j];
}
}
}
return f[1][0]+f[1][1];
}
signed main()
{
cin>>l>>r;
cout<<get(r)-get(l-1)<<endl;
return 0;
}
数位DP代码 求l~r中所有数的数位之和
点击查看代码
//数位DP代码 求l~r中所有数的数位之和
//翻译:把l~r每个数各个数位上的数相加
#include <bits/stdc++.h>
using namespace std;
int l,r,f[30][2],x[maxn],g[30][2];//f[i][j]代表已经把yn~yi填好 j=0代表yn~yi<xn~xi否则等于的情况下有多少种可能性
int get(int x_)//求0~x_有多少个数
{
int n=0;
while(x_!=0)
{
n++;
x[n]=x_%10;
x_=x_/10;
}
memset(f,0,sizeof(f));
f[n+1][1]=1;
g[n+1][1]=0;
for(int i=n;i>=1;i--)//当前要填yi
{
for(int j=0;j<2;j++)
{
int up=9;
if(j==1)
{
up=x[i];
}
for(int k=0;k<=up;k++)
{
f[i][(j==1)&&(k==up)]+=f[i+1][j];
g[i][(j==1)&&(k==up)]+=g[i+1][j]+k*f[i+1][j];
}
}
}
return g[1][0]+g[1][1];
}
signed main()
{
cin>>l>>r;
cout<<get(r)-get(l-1)<<endl;
return 0;
}
浮点数转整数代码
点击查看代码
//浮点数转整数代码
#include <bits/stdc++.h>
using namespace std;
double a;
int main()
{
cin>>a;
cout<<(long long)a;
return 0;
}
加减乘取模代码
点击查看代码
//加减乘取模代码
#include<bits/stdc++.h>
using namespace std;
signed main(){
//加法
x=(a+b)%p;
x=(0ll+a+b+c)%p;
x=((a+b)%p+c)%p;
//减法
x=((a-b)%p+p)%p;
//乘法
x=1ll*a*b%p;
x=1ll*a*b%p*c%p;
}
manacher代码 找最长的回文子串,问最长的长度是多少
点击查看代码
//manacher代码 时间复杂度:O(n)
//找最长的回文子串,问最长的长度是多少
#include<bits/stdc++.h>
using namespace std;
int f[maxn],n;//f[i] 代表以 i 为中心的最长回文子串的 右端点 - i
char s[maxn];
void Manacher()
{
f[1]=0;
int nowmid=1;//nowmid 当前右端点最靠右的回文子串的中心
int nowr=1;//nowr 当前右端点最靠右的回文子串的右端点
for(int i=2;i<=n;i++)//要算 f[i]
{
//抄答案
if(i<=nowr)
{
f[i]=min(f[2*nowmid-i],nowr-i);
}
//暴力向左右扩展
while(true)
{
int pl=i-f[i]-1;
int pr=i+f[i]+1;
if(pl>=1&&pr<=n&&s[pl]==s[pr])f[i]++;
else break;
}
//更新右端点最靠右的回文子串
if(i+f[i]>nowr)
{
nowmid=i,nowr=i+f[i];
}
}
}
signed main()
{
cin>>s+1;
n=strlen(s+1);
for(int i=n;i>=1;i--)
{
s[i*2]=s[i];
}
for(int i=1;i<=2*n+1;i+=2)
{
s[i]='^';
}
n=n*2+1;
Manacher();
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,f[i]);
}
cout<<ans<<endl;
return 0;
}
判断一个数是不是质数代码
点击查看代码
//判断一个数是不是质数
#include <bits/stdc++.h>
using namespace std;
int f;
bool is_prime(int n)//检查n是不是质数
{
if(n<2)
{
return false;
}
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
return false;
}
}
return true;
}
signed main()
{
cin>>f;
if(false)
{
cout<<"YES";
}
else
{
cout<<"NO";
}
return 0;
}
博弈论nim游戏代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int T,i,s;
cin>>T;
while(T--)
{
int n,x;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>x;
s=(i==1?x:s^x);
}
printf(s?"Yes\n":"No\n");
}
}
三维凸包代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
const double eps=1e-9;
int n,cnt,vis[N][N];
double ans;
double Rand(){return rand()/(double)RAND_MAX;}
double reps(){return (Rand()-0.5)*eps;}
struct Node
{
double x,y,z;
void shake(){x+=reps();y+=reps();z+=reps();}
double len(){return sqrt(x*x+y*y+z*z);}
Node operator - (Node A){return (Node){x-A.x,y-A.y,z-A.z};}
Node operator * (Node A){return (Node){y*A.z-z*A.y,z*A.x-x*A.z,x*A.y-y*A.x};}
double operator & (Node A){return x*A.x+y*A.y+z*A.z;}
}A[N];
struct Face
{
int v[3];
Node Normal() {return (A[v[1]]-A[v[0]])*(A[v[2]]-A[v[0]]);}
double area() {return Normal().len()/2.0;}
}f[N],C[N];
int see(Face a,Node b){return ((b-A[a.v[0]])&a.Normal())>0;}
void Convex_3D()
{
f[++cnt]=(Face){1,2,3};
f[++cnt]=(Face){3,2,1};
for(int i=4,cc=0;i<=n;i++)
{
for(int j=1,v;j<=cnt;j++)
{
if(!(v=see(f[j],A[i])))
C[++cc]=f[j];
for(int k=0;k<3;k++)
vis[f[j].v[k]][f[j].v[(k+1)%3]]=v;
}
for(int j=1;j<=cnt;j++)
for(int k=0;k<3;k++)
{
int x=f[j].v[k],y=f[j].v[(k+1)%3];
if(vis[x][y]&&!vis[y][x])
C[++cc]=(Face){x,y,i};
}
for(int j=1;j<=cc;j++)
f[j]=C[j];
cnt=cc,cc=0;
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>A[i].x>>A[i].y>>A[i].z,A[i].shake();
Convex_3D();
for(int i=1;i<=cnt;i++)
ans+=f[i].area();
printf("%.3f",ans);
}
模意义下的乘法逆元代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
long long n,p,inv[20000528];
signed main()
{
scanf("%d %d",&n,&p);
inv[1]=1;
printf("%ld\n",inv[1]);
for(int i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i]%p;
printf("%ld\n",inv[i]);
}
return 0;
}
中国剩余定理(CRT)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
long long a[5211314], b[5211314], n;
void Exgcd(long long a, long long b, long long &x, long long &y)
{
if (b == 0)
{
x = 1;
y = 0;
return;
}
long long x1, y1;
Exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - a / b * y1;
return;
}
long long CRT(long long m[], long long r[])
{
long long M = 1, ans = 0;
for (int i = 1; i <= n; ++ i)
{
M *= m[i];
}
for (int i = 1; i <= n; ++ i)
{
long long c = M / m[i], x, y;
Exgcd(c, m[i], x, y);
ans = (ans + r[i] * c * x % M) % M;
}
return (ans + M) % M;
}
int main()
{
scanf("%lld", &n);
for (int i = 1; i <= n;i++)
{
scanf("%lld%lld", a + i, b + i);
}
printf("%lld\n", CRT(a, b));
return 0;
}
最小生成树kruskal代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long f[500007],n,m,num,cnt,ans;
struct Edge{
long long u,v,w ;
}e[500007];
long long find(long long k){return (f[k]==k)?k:f[k]=find(f[k]);}
bool cmp(Edge a, Edge b){return a.w<b.w;}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
for(int i=1;i<=n;i++)
f[i]=i;
sort(e+1,e+m+1,cmp);
for(int i=0;i<=m;i++)
{
if(find(e[i].u)==find(e[i].v))
continue;
f[find(e[i].u)]=find(e[i].v);
ans+=e[i].w;
if(++num==n-1) break;
}
cout<<ans<<endl;
return 0;
}
最小生成树prim代码
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define INF INT_MAX
using namespace std;
const int M=3005;
const int N=2005;
int n,m,tot,ans;
int head[N],dist[N],vis[N];
struct node{
int to,next,w;
}edge[M<<1];
void addedge(int x, int y, int z)
{
tot++;
edge[tot].to = y;
edge[tot].w = z;
edge[tot].next = head[x];
head[x] = tot;
}
void Prim()
{
for(int i = head[1]; i; i = edge[i].next)
dist[edge[i].to] = min(dist[edge[i].to], edge[i].w);
int u = 1;
for(int i = 1; i < n; i++)
{
int minn=INF;
vis[u]=true;
for(int j = 1; j <= n; j++)
{
if(!vis[j] && dist[j] < minn)
{
u = j;
minn = dist[j];
}
}
ans += minn;
for(int k = head[u]; k; k = edge[k].next)
{
int v = edge[k].to;
if(dist[v] > edge[k].w && !vis[v])
dist[v] = edge[k].w;
}
}
}
void Init()
{
for(int i=1;i<=n;i++)
dist[i]=INF;
dist[1]=0;
return;
}
int main()
{
cin>>n>>m;
while(m--)
{
int x, y, z;
cin>>x>>y>>z;
addedge(x,y,z);
addedge(y,x,z);
}
Init();
Prim();
cout<<ans<<endl;
return 0;
}
字符串哈希代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull base=131,a[10010];
char s[10010];
int n,ans=1;
const ull mod=212370440130137957ll;
ull hashs(char s[])
{
int len=strlen(s);
ull ans=0;
for(int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod;
return ans;
}
main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",s);
a[i]=hashs(s);
}
sort(a+1,a+n+1);
for(int i=2;i<=n;i++)
if(a[i]!=a[i-1])
ans++;
printf("%d\n",ans);
}
快速排序代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,a[1000001];
void quicksort(int l, int r)
{
int mid=a[(l+r)/2];
int i=l,j=r;
while(i<=j)
{
while(a[i]<mid) i++;
while(a[j]>mid) j--;
if(i<=j)
{
swap(a[i],a[j]);
i++;
j--;
}
}
if(l<j)
quicksort(l,j);
if(i<r)
quicksort(i,r);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
quicksort(1,n);
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
}
线性筛代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool isprime[100000010];
int prime[6000010],n,q,k,cnt;
void getprime(int n)
{
memset(isprime,1,sizeof(isprime));
isprime[1]=0;
for(int i=1;i<=n;i++)
{
if(isprime[i])
prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
{
isprime[i*prime[j]]=0;
if(i%prime[j]==0)
break;
}
}
}
int main()
{
cin>>n>>q;
getprime(n);
while(q--)
{
cin>>k;
cout<<prime[k]<<endl;
}
}
小根堆代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> >q;
int n,x;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
if(x==1)
{
cin>>x;
q.push(x);
}
else
if(x==2)
cout<<q.top()<<endl;
else
q.pop();
}
}
并查集代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,s,ans,f[10010],p1,p2,p3;
int find(int k)
{
if(f[k]==k)
return k;
return f[k]=find(f[k]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
cin>>p1>>p2>>p3;
if(p1==1)
f[find(p2)]=find(p3);
else
cout<<(find(p2)==find(p3)?"Y":"N")<<endl;
}
return 0;
}
有理数取余代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int mod=19260817;
int a,b;
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-')
{
f=-1;
}
ch=getchar();
}
while(ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
x=x%mod;
ch=getchar();
}
return x*f;
}
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,x,y);
int temp=x;
x=y;
y=temp-(a/b)*y;
return d;
}
int main()
{
a=read();
b=read();
if(b==0)
{
puts("Angry");
return 0;
}
int x,y;
int d=exgcd(b,mod,x,y);
x=(x%mod+mod)%mod;
printf("%lld\n",a*(long long )x %mod);
return 0;
return 0;
}
手写随机堆代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int seed=2322333;
bool random_bool(){
seed = seed * 997 % 100007;
return seed & 32; // 取出二进制从低到高第 6 位
}
struct node{
int val;
int lson, rson; // int son[2]; son[L] son[R];
};
node a[100005];
int cnt; // 根节点是1号点
void insert(int k, int val){ // 节点k在的树,插入 val
if(val > a[k].val) // 进来的val 更大,直接篡位
swap(val, a[k].val); // 旧王退位
if(a[k].lson == 0){ // 左儿子这边一个节点都没有
a[k].lson = ++cnt; // 安置在这里,新建一个点
a[cnt].val = val;
}else if(a[k].rson == 0){ // 如果左边不空,右边空,那就去右边
a[k].rson = ++cnt;
a[cnt].val = val;
}else if(random_bool()) // 不然的话,随机一边递归下去
insert(a[k].lson, val);
else
insert(a[k].rson, val);
}
bool is_leaf(int k){
return a[k].lson == 0 && a[k].rson == 0;
}
void remove(int k){ // 把这个节点的值删掉
if(a[a[k].lson].val > a[a[k].rson].val){
a[k].val = a[a[k].lson].val; // 把左儿子拿过来替补
if (!is_leaf(a[k].lson))
remove(a[k].lson);
else
a[k].lson = 0;
}else{
a[k].val = a[a[k].rson].val; // 把右儿子拿过来替补
if (!is_leaf(a[k].rson))
remove(a[k].rson);
else
a[k].rson = 0;
}
}
int root, ele_cnt;
void push(int val){ // 堆里插入一个值为val的元素
ele_cnt++; // 给这个新的元素分配一个点
insert(root, val);
}
void pop(){
ele_cnt--;
remove(root);
}
int top(){
return a[root].val;
}
bool empty(){
return ele_cnt == 0;
}
int main(){
a[0].val = -999999999;
a[1].val = -999999999;
root=cnt=1;
push(233);
push(666);
push(-3);
push(99);
//访问队列首元素,注意这里不是像队列一样使用front和back来访问首元素和尾元素
cout << top() <<endl;
//删除首元素
pop();
cout << top() <<endl;
//判断队列是否为空
if(!empty()) cout << "队列不为空" <<endl;
//输出剩下元素
while(!empty()){
cout << top() << " ";
pop();
}
return 0;
}
A+B A-B A*B A/B A%B高精度代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#ifndef _INTEGER_HPP_
#define _INTEGER_HPP_
namespace PsephurusGladius
{
namespace integer
{
const int Max_Integer_Length=1e4;
char char_array_temp[Max_Integer_Length];
class Integer{
private:
int digit[Max_Integer_Length],count;
short signum;
public:
Integer(void);
Integer(const Integer &target);
Integer(const long long &target);
Integer(const char *target);
operator char*(void);
void zero(void);
friend std::istream& operator >>(std::istream &Cin,Integer &target);
friend std::ostream& operator <<(std::ostream &Cout,const Integer &target);
Integer absolute_value(void)const;
Integer opposite_number(void)const;
Integer operator -(void)const;
friend bool operator <(const Integer &target1,const Integer &target2);
friend bool operator >(const Integer &target1,const Integer &target2);
friend bool operator <=(const Integer &target1,const Integer &target2);
friend bool operator >=(const Integer &target1,const Integer &target2);
friend bool operator ==(const Integer &target1,const Integer &target2);
friend bool operator !=(const Integer &target1,const Integer &target2);
friend Integer operator +(const Integer &target1,const Integer &target2);
friend Integer operator -(const Integer &target1,const Integer &target2);
friend Integer operator *(const Integer &target1,const Integer &target2);
friend Integer operator /(const Integer &target1,const Integer &target2);
friend Integer operator %(const Integer &target1,const Integer &target2);
Integer& operator ++(void);
Integer operator ++(int);
Integer& operator --(void);
Integer operator --(int);
Integer operator +=(const Integer &target);
Integer operator -=(const Integer &target);
Integer operator *=(const Integer &target);
Integer operator /=(const Integer &target);
Integer operator %=(const Integer &target);
};
inline Integer::Integer(void):count(0),signum(0){
memset(digit,0,sizeof(digit));
}
inline Integer::Integer(const Integer &target):count(target.count),signum(target.signum){
memcpy(digit,target.digit,sizeof(digit));
}
inline Integer::Integer(const long long &target){
memset(digit,0,sizeof(digit));
signum=target<0?-1:(target>0?1:0);
count=-1;
long long temp=target;
do{
digit[++count]=temp%10000;
temp/=10000;
}
while(temp);
}
inline Integer::Integer(const char *target){
memset(digit,0,sizeof(digit));
int start=0,len=strlen(target);
if(target[0]=='-'&&(target[1]!='0'||len!=2)){
signum=-1;
++start;
}
else if(target[0]!='0'||len!=1)signum=1;
else signum=0;
count=(len-start-1)/4;
for(int i=start,sum=0;i<len;++i)
{
sum=sum*10+target[i]-'0';
if(!((len-i-1)%4))
{
digit[count-(i-start)/4]=sum;
sum=0;
}
}
while(count>0&&!digit[count])
--count;
}
inline Integer::operator char*(void)
{
memset(char_array_temp,0,sizeof(char_array_temp));
for(int i=count,len=0;i>-1;--i)
{
if(i==count)
{
len+=sprintf(char_array_temp+len,"%d",digit[i]);
continue;
}
len+=sprintf(char_array_temp+len,"%04d",digit[i]);
}
return char_array_temp;
}
inline void Integer::zero(void)
{
memset(digit,0,sizeof(digit));
count=signum=0;
}
inline std::istream& operator >>(std::istream &Cin,Integer &target)
{
scanf("%s",char_array_temp);
target=Integer(char_array_temp);
return Cin;
}
inline std::ostream& operator <<(std::ostream &Cout,const Integer &target)
{
if(target.signum==-1)
printf("-");
printf("%d",target.digit[target.count]);
for(int i=target.count-1;i>-1;--i)
printf("%04d",target.digit[i]);
return Cout;
}
inline Integer Integer::absolute_value(void)const
{
if(!count&&!signum)
return *this;
else
{
Integer result=*this;
result.signum=1;
return result;
}
}
inline Integer Integer::opposite_number(void)const
{
Integer result=*this;
result.signum*=-1;
return result;
}
Integer Integer::operator -(void)const
{
return opposite_number();
}
inline bool operator <(const Integer &target1,const Integer &target2)
{
if(target1.signum!=target2.signum)
return target1.signum<target2.signum;
if(target1.signum==-1)
return target2.absolute_value()<target1.absolute_value();
if(target1.count!=target2.count)
return target1.count<target2.count;
for(int i=target1.count;i>-1;--i)
if(target1.digit[i]!=target2.digit[i])
return target1.digit[i]<target2.digit[i];
return false;
}
inline bool operator >(const Integer &target1,const Integer &target2)
{
return !(target1<target2)&&!(target1==target2);
}
inline bool operator ==(const Integer &target1,const Integer &target2)
{
if(target1.signum!=target2.signum||target1.count!=target2.count)
return false;
for(int i=target1.count;i>-1;--i)
if(target1.digit[i]!=target2.digit[i])
return false;
return true;
}
inline bool operator <=(const Integer &target1,const Integer &target2)
{
return target1<target2||target1==target2;
}
inline bool operator >=(const Integer &target1,const Integer &target2)
{
return !(target1<target2);
}
inline bool operator !=(const Integer &target1,const Integer &target2)
{
return !(target1==target2);
}
inline Integer operator +(const Integer &target1,const Integer &target2)
{
if(target1.signum!=target2.signum)
if(!target1.signum||!target2.signum)
return target1.signum?target1:target2;
else return target1.signum<target2.signum?target2-target1.absolute_value():target1-target2.absolute_value();
Integer result;
result.count=target1.count<target2.count?target2.count:target1.count;
result.signum=target1.signum;
for(int i=0;i<=result.count;++i)
{
result.digit[i]+=target1.digit[i]+target2.digit[i];
result.digit[i+1]=result.digit[i]/10000;
result.digit[i]%=10000;
}
if(result.digit[result.count+1])
++result.count;
return result;
}
inline Integer operator -(const Integer &target1,const Integer &target2)
{
if(target1.signum!=target2.signum)
if(!target1.signum||!target2.signum)
return target1.signum?target1:target2.opposite_number();
else return target1.signum<target2.signum?(target1.absolute_value()+target2).opposite_number():target1+target2.absolute_value();
if(target1<target2)
return (target2-target1).opposite_number();
Integer result;
if(target1==target2)
return result;
result.count=target1.count;
result.signum=1;
for(int i=0;i<=result.count;++i)
{
result.digit[i]+=target1.digit[i]-target2.digit[i];
if(result.digit[i]<0)
{
--result.digit[i+1];
result.digit[i]+=10000;
}
}
while(result.count>0&&!result.digit[result.count])
--result.count;
return result;
}
inline Integer operator *(const Integer &target1,const Integer &target2)
{
Integer result;
if(!target1.signum&&!target2.signum)
return result;
result.signum=target1.signum*target2.signum;
result.count=target1.count+target2.count+1;
for(int i=0;i<=target1.count;++i)
for(int j=0;j<=target2.count;++j)
{
result.digit[i+j]+=target1.digit[i]*target2.digit[j];
result.digit[i+j+1]+=result.digit[i+j]/10000;
result.digit[i+j]%=10000;
}
while(result.count>0&&!result.digit[result.count])
--result.count;
return result;
}
inline Integer operator /(const Integer &target1,const Integer &target2)
{
Integer result,temp,now;
if(!target1.signum||target1.absolute_value()<target2.absolute_value())
return result;
if(!target2.signum)
throw std::logic_error("divide by zero!");
result.signum=target1.signum*target2.signum;
result.count=target1.count;
now.signum=1;
for(int i=result.count;i>-1;--i)
{
for(int j=now.count;j>-1;--j)
now.digit[j+1]=now.digit[j];
now.digit[0]=target1.digit[i];
if(now.digit[now.count+1])
++now.count;
now.signum=1;
if(now<target2)
continue;
int left=0,right=9999;
while(left<right)
{
int mid=(left+right)/2;
if(target2*Integer(mid)<=now)
left=mid+1;
else
right=mid;
}
result.digit[i]=left-1;
now-=Integer(left-1)*target2;
}
while(result.count>0&&!result.digit[result.count])
--result.count;
return result;
}
inline Integer operator %(const Integer &target1,const Integer &target2)
{
return target1-target1/target2*target2;
}
inline Integer& Integer::operator ++(void)
{
return *this=*this+Integer(1ll);
}
inline Integer Integer::operator ++(int)
{
Integer result=*this;
++*this;
return result;
}
inline Integer& Integer::operator --(void)
{
return *this=*this-Integer(1ll);
}
inline Integer Integer::operator --(int)
{
Integer result=*this;
--*this;
return result;
}
inline Integer Integer::operator +=(const Integer &target)
{
return *this=*this+target;
}
inline Integer Integer::operator -=(const Integer &target)
{
return *this=*this-target;
}
inline Integer Integer::operator *=(const Integer &target)
{
return *this=*this*target;
}
inline Integer Integer::operator /=(const Integer &target)
{
return *this=*this/target;
}
inline Integer Integer::operator %=(const Integer &target)
{
return *this=*this%target;
}
}
}
#endif
using namespace PsephurusGladius;
using namespace integer;
Integer a,b;
signed main(){
cin>>a>>b;
cout<<a+b<<endl<<a-b<<endl<<a*b<<endl<<a/b<<endl<<a%b;
return 0;
}
倍增LCA代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=110; // 最多的点数
const int maxm=210; // 最多的边数
int n;
vector<int> g[maxn]; // g[i] 存着与i相连的点
void AddEdge(int u,int v) { // 添加一个(u,v)双向边
g[u].push_back(v);
g[v].push_back(u);
}
int fa[20][maxn],dep[maxn]; // fa[i][k] 表示k节点往上走 2的i次方步到哪个祖先
// dep 存的是节点深度
void dfs(int x, int father){ // 树上遍历建立倍增表
fa[0][x] = father; // 记录一下
dep[x] = dep[father]+1; // 根节点深度为 1
for(int i=1;(1<<i)<=dep[x];i++)
fa[i][x]=fa[i-1][fa[i-1][x]];
for(auto to: g[x]) //遍历自己的儿子
if(to != father) // 别跑回父亲去了
dfs(to, x);
}
int lca(int x,int y) {
if(dep[x]<dep[y]) swap(x,y); // 先保证 x的深度比y的深度深
// 假设 dep[x] - dep[y] = 6 = 4 + 2
// 6 的二进制是 0110,对应了 4 (0100) 和 2(0010)
for(int i=19;i>=0;i--)
if( (dep[x]-dep[y]) & (1<<i)) // 拿2的整数次幂凑出我们要走的距离
// if( (dep[x]-dep[y]) >= (1<<i)) // 等效写法,能走就走
x=fa[i][x];
if(x == y) return x; // 如果某一个人是另一个人的直接祖先,返回
for(int i=19;i>=0;i--) // 倍增找深度最浅不相同的祖先
if(fa[i][x] != fa[i][y])
x=fa[i][x], y=fa[i][y];
return fa[0][x]; // 返回某个的直接父亲就是最近公共祖先
}
int main() {
n=5;
AddEdge(1,2);
AddEdge(2,3);
AddEdge(2,5);
AddEdge(5,6);
AddEdge(1,4);
AddEdge(4,7);
AddEdge(7,8);
AddEdge(7,9);
dfs(1,0);
printf("%d\n",lca(3,9));
return 0;
}
vector存图代码
点击查看代码
#include<vector>
#include<cstdio>
#include<iostream>
using namespace std;
vector<int> g[100005]; // g[i] 里面存着所有与 i 相连的点的编号
int main(){
vector<int> v; // 数组v
v.push_back(1);
v.push_back(1);
v.push_back(4);
v.push_back(1);
v.push_back(1);
v.push_back(4);
printf("%d\n", v.size());
printf("%d\n", v[5]);
for(int i=0;i<v.size();i++)
printf("%d\n", v[i]);
for(auto i: v)
printf("%d\n", i);
return 0;
}
ST表代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
inline int read(){
int x=0,f=1; // x 表示我们读到值, f表示数的符号
char ch=getchar(); // getchar 是获得输入内容的下一个字符
while (!isdigit(ch)){
if (ch=='-') f=-1; // 说明输入一个负数
ch=getchar(); // 不然的话下一个字符
}
while (isdigit(ch)){
x=x*10+(ch-'0'); // x=123, x=1234 = 123 * 10 + 4
ch=getchar(); // 读下一个字符
}
return x*f; // 数字乘上符号
}
const int INF = 0x3f3f3f3f; // 大概是 10的9次方多一些 INF+INF < 2^31
int n,m; // 假设我们序列长度为 n,一共有m个询问。
int a[N]; // a是初始值序列
//ST表
int st[20][N]; // st[i][L] 表示从 L 开始长度为 2^i 的区间的最大值
int logn[N]; // logn[i] 表示 2^logn[i] 次方不超过 i
// logn[1] = 0, logn[2] = 1, logn[3] = 1, logn[4] = 2, logn[5] = 2, logn[6]=2, logn[7]=2, logn[8]=3
// logn[i] = logn[i/2] + 1
void pre(){
for(int i=2;i<=n+2;i++) logn[i]=logn[i>>1]+1; // 预处理一下logn数组
for(int i=1;i<=n;i++) // 初始化st 表第一行 st[0], 即长度为1的序列
st[0][i]=a[i];
for(int i=1;(1<<i)<=n;i++) // (1<<i) = 2的i次方,
for(int j=1;j+(1<<i)-1<=n;j++){ // j 是区间起始位置, j+(1<<i)-1 是区间结尾
st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
// 例如 i=2, j=3, st[2][3] 表示区间 [3,6]
// 那么就是 st[2][3] = max(st[1][3], st[1][5]);
// 其中st[1][3] 表示区间 [3, 4], st[1][5] 表示区间 [5, 6]
}
}
int getmax(int l,int r){ // 查询[l, r]
if(l>r) return -INF; //
int d=logn[r-l+1]; // 不超过区间长度的最大的2的整数次幂
return max(st[d][l],st[d][r-(1<<d)+1]); // l开始长度为2的d次方的,和结束于r的长度为2的d次方的
}
int main(){
n = read(), m = read();
for(int i=1;i<=n;i++) a[i]=read();
pre();
for(int i=1;i<=m;i++){
int l=read(), r=read();
printf("%d\n", getmax(l,r));
}
return 0;
}

浙公网安备 33010602011771号