蓝桥杯考纲和知识点总结

蓝桥杯考纲和知识点总结

1.一些常用知识点

快速幂

快速幂很常用,要熟练

求 m^k mod p,时间复杂度 O(logk)。

int qmi(int m, int k, int p)
{
    int res = 1 % p, t = m;
    while (k)
    {
        if (k&1) res = res * t % p;
        t = t * t % p;
        k >>= 1;
    }
    return res;
}

卡特兰数

这个有时候会遇到,比如括号匹配数,求某种排列数量的题都可以带进来试试,求组合数的方法在6部分。

给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为: Cat(n) = C(2n, n) / (n + 1)

中位数

用对顶堆维护,前面大顶堆,后面小顶堆,大顶堆堆顶即中位数,来了一个数跟中位数比较,小的放大顶堆,大的放小顶堆,再维护两个堆的大小保证大顶堆的大小比小顶堆最多大1.

第k大的数

排序O(nlogn)
对顶堆O(nlonn)

类似快排的思想O(n)

逆序对

归并排序O(nlogn)求出逆序对个数
void merge(int l,int mid,int r){
	//合并a[l~mid]与a[mid+1~r]
	//a是待排序数组,b是临时数组,cnt是逆序对个数
	int i = l,j = mid + 1;
	for(int k = l,k <= r;k++){
		if(j > r || i <=mid && a[i] <= a[j]) b[k] = a[i++];
		else b[k] = a[j++],cnt += mid-i+1;
	}
	for(int k = l;k <= r;k++) a[k] = b[k];
}

//还可以用树状数组求

位运算

求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n

2.基础算法

简单:枚举 倍增 离散化 差分 前缀和

中等:分治 贪心 Huffman编码 尺取法 二分 三分 ST算法

比较重要的是前缀和、差分算法,前缀和容易考到二维的情况。

前缀和

序列A某个下标区间的数的和 可表示为: sum(l,r) = S[r] - S[l-1]
二维前缀和:S[i,j] = S[i-1,j] + S[i,j-1] - S[i-1,j-1] + A[i,j]

差分

对于数列A,差分数列B: B[1]=A[1],B[i] = A[i] - A[i-1]
前缀和 与 差分 互为逆运算
差分数列的前缀和就是原数列A
把A的区间[l,r]加d,就是把差分B[l]+d,B[r+1]-d
差分可以应用在树上

二分

常规二分方法如下,不太好记,简单情况建议使用lower_bound(),也在下面介绍。

当问题的答案具有单调性,就可以通过二分法把求解转化为判定。
整数二分:
以下代码以l=r结束:
在升序a中找到>=x的最小一个:
while(l < r){
	int mid = (l + r) >> 1;
	if(a[mid] >= x) r = mid;else l = mid + 1;
}
return a[l];

在升序a中找到<=x的最大一个:
while(l < r){
	int mid = (l + r + 1) >> 1;
	if(a[mid] <= x) l = mid;else r = mid - 1;
}
return a[l];
#注:用右移运算>> 而不是整除/,因为前者是向下取整,后者是向0取整,在二分值域包含负数时后者不能正常取整。
#注:mid = (l+r) >> 1 不会取到r,mid = (l+r+1) >> 1 不会取到l,可以用这个性质处理无解的情况,把原区间[1,n]分别扩大为[1,n+1]和[0,n],如果最后二分终止于扩大后的下标,则无解。 

#实域二分:
一般要保留k位小数时,eps=10^-(k+1)
while(l + 1e-5 < r){
	double mid = (l + r) / 2;
	if(calc(mid)) r = mid;else l = mid;
}
或 精度更高的:
for(int i = 0;i < 100;i++){
	double mid = (l + r) / 2;
	if(calc(mid)) r = mid; else l = mid;
}
#三分求单峰函数极值:

取 lmid 和 rmid
1. f(lmid) < f(rmid) 则lmid和rmid都在极大值左侧,或在极大值两侧。无论哪种情况极大值都在lmid右侧,可令 l = lmid;
2. f(lmid) > f(rmid) 同理,令 r = rmid;
如果取lmid与rmid为三等分点,那么定义域每次缩小1/3。
如果取lmid与rmid为二等分点两侧极其接近的地方,那么定义域每集近缩小1/2。
时间复杂度为log级别
#二分答案转化为判定:

把求最优的问题,转化为给定一个值mid,判定是否存在一个可行方案评分达到mid的问题。
lower_bound()
lower_bound() & upper_bound()
lower_bound() : 寻找≥x的第一个元素的位置
upper_bound() : 寻找>x的第一个元素的位置

lower_bound(abc.begin(),abc.end(),x)   
upper_bound 同理
函数返回的是迭代器,如何转成下标索引呢?减去头迭代器即可,因为迭代器 - 迭代器=两个迭代器的距离
即 lower_bound(abc.begin(),abc.end(),x)-abc.begin()

以上就可以解决很多问题了,但是当我们的数组中使用的是自己定义的结构体,可以采用下述方法:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef struct Student{
	int _id;  //学号
	int _num; //排名
	Student(int id, int num)
		:_id(id)
		, _num(num)
	{}
}Stu;
 
struct CompareV{
	bool operator() (const Stu& s1,  const Stu& s2)//  排名升序
	{	
		return s1._num < s2._num;
	}
};
 
int main()
{
	vector<Stu> vS = { { 101, 34 }, { 103, 39 }, { 102, 35 } };
	//结构体数组在此排序,排序后才能正常使用lower_bound
	auto iter = lower_bound(vS.begin(), vS.end(), Stu(200,33), CompareV());
	cout << iter - vS.begin() << endl; //我们就找到了按仿函数排序(找排名比33大的位置 就是0)
	system("pause");
}

如上lower_bound()就可以帮助我们解决90%二分的问题了。

另外排序结构体:

bool cmp(Stu a,Stu b){
    return a._num < b._num;
}
sort(vS.begin(),vS.end(),cmp);

3.搜索

简单:DFS、BFS 拓扑排序

中等:剪枝 记忆化 爬山算法 随机增量法 模拟退火

难:A* IDA*

深搜和宽搜可以说是蓝桥杯这种比赛的利器,后面的难题不会写可以考虑(我觉得大多数人都是不会最优解法的,或者时间不够,果断暴力+剪枝)

DFS

# 树与图的深度优先遍历,树的DFS序、深度和重心
void dfs(int x){
	v[x] = 1;//记录点x被访问,v是visit
	for(int i = head[x];i;i = next[i]){
		int y = ver[i];
		if(v[y]) continue;
		dfs(y);
	}
}

BFS

void bfs(){
	memset(d,0,sizeof(d));
	queue<int> q;
	while(q.size() > 0){
		int x = q.front();q.pop();
		for(int i = head[x];i;i = next[i]){
			int y = ver[i];
			if(d[y]) continue;
			d[y] = d[x] + 1;//深度
			q.push(y);
		}
	}
}

拓扑排序

对于每条边(x,y),x在A中都出现在y之前。
思想:不断选择入度为0的节点x,并把x连向的点入度减1.
拓扑排序可以判定图中是否存在环。若拓扑序列长度小于节点个数则存在环。

void add(int x,int y){//在邻接表中添加一条有向边
	ver[++tot] = y,next[tot] = head[x],head[x]=tot;
	deg[y]++;
}
void topsort(){ //拓扑排序
	queue<int> q;
	for(int i = 1;i <= n;i++){
		if(deg[i] == 0) q.push(i);
	}
	while(q.size()){
		int x = q.front();q.pop();
		a[++cnt] = x;
		for(int i = head[x];i;i = next[i]){
			int y = ver[i];
			if(--deg[y] == 0) q.push(y);
		}
	}
}
int main(){
	cin >> n >> m;//点数,边数
	for(int i = 1;i <= m;i++){
		int x,y;
		cin >> x >> y;
		add(x,y);
	}
	topsort();
	for(int i = 1;i <= cnt;i++){
		cout << a[i] <<" ";
	}
}
#计算从每个点出发能到达的点数量,用拓扑排序。再用状压f[x]=N表示能到i(i位为1)。

剪枝

剪枝这一块还是比较难的,4、5最常用

1. 优化搜索顺序(树底要小)
2. 排除等效冗余(避免重叠、混淆层次与分支,避免遍历若干棵覆盖同一状态空间的等效搜索树)
3. 可行性剪枝(无法到达边界、未来预算)
4. 最优性剪枝(当前代价超过最优解)
5. 记忆化(记录是否访问过)

4.高级数据结构

简单:并查集 分块 字典树

中等:莫队算法(树上莫队) 树状数组 二叉搜索树

难:splay树 LCT树套树 猫树 CDQ分治 舞蹈树 左偏树 后缀平衡树 KDtree 线段树 可持久化线段树 treap树 替罪羊树 块状链表

并查集

并查集是一种可以动态维护若干个不重叠的集合,并支持合并与查询的数据结构。
#路径压缩与按秩合并
每次在只需get方法的同时,把访问过的每个节点都直接指向树根

int fa[SIZE];//1.定义并查集
for(int i = 1;i <= n;i++) fa[i] = i;//2.并查集初始化;
int get(int x){//3.并查集的get操作
	if(x == fa[x]) return x;
	return fa[x] = get(fa[x]);//路径压缩
}
void merge(int x,int y){//4.merge合并
	fa[get(x)] = get(y);
}
#扩展域 与 边带权 的并查集
用d[x]保存节点x到父节点fa[x]之间的边权,每次路径压缩后更新d值。(边带权)

int get(int x){
	if(x == fa[x]) return x;
	int root = get(fa[x]);//递归计算集合代表
	d[x] += d[fa[x]]; //维护d数组--对边权求和
	return fa[x] = root;
}
void merge(int x,int y){
	x = get(x),y = get(y);
	fa[x] = y,d[x] = size[y];//size数组在树根记录集合大小
	size[y] += size[x];
}

字典树

#字典树
用于实现字符串快速检索
#统计前缀个数、10^5个数两两异或最大值(每次尽量查询和当前位不同的)。
int trie[SIZE][26],tot=1;//初始化,假设字符串由小写字母组成
void insert(char* str){
	int len = strlen(str),p = 1;
	for(int k = 0;k < len;k++){
		int ch = str[k] - 'a';
		if(trie[p][ch] == 0) trie[p][ch] = ++ tot;
		p = trie[p][ch];
	}
	end[p] = true;
}
bool search(char* str){//检索是否存在
	int len = strlen(str),p = 1;
	for(int k = 0;k < len;k++){
		p = trie[p][str[k]-'a'];
		if(p == 0) return false;
	}
	return end[p];
}
树上 x到y路径边权异或值,等于根到x路径异或值 异或 根到y路径异或值,因为两条路重叠的边恰好抵消掉了。

树状数组

和前缀和一样维护区间和,不同点在于,前缀和维护的是静态数组,树状数组支持单点修改。

1.每个内部节点c[x]保存以它为根节点的子树中所有叶节点的和。
2.每个内部节点c[x]的子节点个数等于lowbit(x)的位数。
3.除树根外,每个内部节点c[x]的父节点是c[x+lowbit(x)]。
4.树的深度为O(logN)。
树状数组有两个基本操作
第一个是查询前缀和
int ask(int x){//1~x的和
	int ans = 0;
	for(;x;x -= x&-x) ans += c[x];
	return ans;
}
[l,r] : ask(r)-ask(l-1)
第二个是单点增加(同时维护前缀和)
void add(int x,int y){
	for(;x<=N;x += x&-x) c[x] += y;
}
初始化:add(x,a[x])
#树状数组与逆序对
集合a,用t[val]表示val在a中出现的次数,则t[l,r]区间和表示a中在[l,r]区间的数有多少个。
树状数组维护前缀和允许在集合a修改。
利用树状数组求逆序对:
1.在序列a的数值范围上建立树状数组,初始化为全0。
2.倒序扫描给定的序列a,对于每个数a[i]:
	在树状数组中查询前缀和[1,a[i]-1],累加到答案ans中。
	执行"单点增加"操作,即把位置a[i]上数加1(相当于t[a[i]]++),同时维护t的前缀和,表示a[i]又出现1次。
3.ans即为所求

for(int i = n;i;i--){
	ans += ask(a[i]-1);
	add(a[i],1);
}

注:同理可以用树状数组分别求i位置前后又多少个数比i大(小)
#树状数组的扩展应用

可以把 区间增加+单点查询 变为树状数组擅长的 单点增加+区间增加

5.动态规划

简单:线性dp(01问题,背包九讲,最长公共子序列,最长递增子序列,编辑距离,最小划分,行走问题,矩阵最长递增路径,子集和问题,矩阵链乘法,布尔括号问题)

中等:区间dp,状压dp,树形dp,计数dp,概率dp

难:插头dp,基环树dp,dp优化(数据结构优化,单调队列优化,斜率优化,分治优化,四边形不等式优化)

6.数论

简单:余数,gcd,lcm,素数判定,埃氏筛

中等:整数拆分,ExGDC,欧拉筛(线性筛),威尔逊定理,原根,费马小定理,欧拉定理,欧拉函数,整除分块,同余,逆元,高斯消元,中国剩余定理,大步小步发BSGS,积性函数,莫比乌斯反演

素数

大多时候1就够用了,还好记一点。

1.Eratosthenes筛法
任意x的倍数 2x,3x,...,[N/x]*x标记为合数。

2和3都会把6标记为合数,实际上小于x^2的x的倍数在扫描更小的数时已经被标记过了。
对于每个数x,只需要从x^2开始,把x^2,(x+1)*x,...,[N/x]*x标记为合数即可。
void primes(int n){//时间复杂O(NloglogN)
	memset(v,0,sizeof(v));
	for(int i = 2;i <= n;i++){
		if(v[i]) continue;
		cout << i << endl;//i是质数
		for(int j = i;j <= n/i;j++) v[i*j] = 1;
	}
}

2.线性筛法
即使是优化后(从X^2开始),Eratosthenes筛法任然会重复标记合数。12=6*2,12=4*3.
线性筛法通过 从大到小积累质因子 的方式标记每个合数,即12 = 3*2*2一种产生方式。设数组v记录每个数的最小质因子。
1.依次考虑2~N之间的每一个数i。
2.若v[i] = i,说明i是质数,把它保存下来。
3.扫描不大于v[i] 的每一个质数p,令v[i*p]=p。也就是在i的基础上累积一个质因子p。因为p<=v[i],所以p就是合数i*p的最小质因子。
每个合数i*p只会被它的最小质因子p筛一次,时间复杂度为O(N)
int v[MAX_N],prime[MAX_N];
void prime(int n){
	memset(v,0,sizeof(v));//存最小质因子
	m = 0;//质数数量
	for(int i = 2;i <= n;i++){
		if(v[i] == 0){v[i] = i;prime[++m] = i;} //i是质数
		//给当前的数i乘上一个质因子
		for(int j = 1;j <=;j++){
			//i 有比prime[j]更小的质因子,或者超出n的范围,停止循环
			if(prime[j] > v[i] || prime[j] > n/i) break;
			//prime[j]是合数i*prime[j]的最小质因子
			v[i*prime[j]] = prime[j];
		}
	}
	for(int i = 1;i <= m;i++) cout << pirme[i] << endl;
}

最大公约数、最小公倍数

#最大公约数
最大公约数gcd(a,b)
最小公倍数lcm(a,b)
定理:a*b = gcd(a,b) * lcm(a,b)

更相减损术:gcd(a,b) = gcd(b,a-b) = gcd(a,a-b)
gcd(2a,2b) = 2gcd(a,b)

欧几里得算法:gcd(a,b) = gcd(b,a % b)
int gcd(int a,int b){
	reutrn b ? gcd(b,a%b) : a;
}
欧几里得算法时间复杂度为O(log(a+b))。
高精度不易取模,考虑更相减损术

约数个数和约数之和

这个要先分解质因数

p是质数
如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

组合数

递推法求组合数

// c[a][b] 表示从a个苹果中选b个的方案数
for (int i = 0; i < N; i ++ )
    for (int j = 0; j <= i; j ++ )
        if (!j) c[i][j] = 1;
        else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

通过预处理逆元的方式求组合数 (推荐)

首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a, int k, int p)    // 快速幂模板
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
    fact[i] = (LL)fact[i - 1] * i % mod;
    infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}

C[a][b] = fact[a]*infact[b]*infact[a-b]

还有一种情况是,要阶乘的数比较大,我们可以求分母的质因数分解,再求分子的质因数分解,用质因数的个数相减,就出结果的质因数分解,进而求出结果。

7.字符串

简单:字符串处理 字符串Hash

中等:KMP,后缀树,后缀数组,Manacher回文算法,最小表示法

困难:AC自动机,后缀自动机,回文自动机

字符串Hash

字符串hash比较神奇,可以解决很多问题,比如求最长回文子串,马拉车算法也不好记,而字符串Hash也可以O(n)解决;

#字符串Hash

取一固定值P,把字符串看作P进制数,分配一个大于0的数值,代表每种字符。取一固定值M,求出该P进制数对M的余数作为Hash值。一般P=131,13331。M = 2^64,即直接使用unsigned long long存储Hash。溢出时直接取模。手动取模效率低。
    
F[i] = F[i-1] * 131 + (S[i] - 'a' + 1);
value[l,r] = F[r] - F[l-1] * 131^(r-l+1);//所以要维护131的阶乘p[i]

字符串hash O(nlongn) 求最长回文子串
枚举中心,二分出最长的 两边Hash值相等的子串

另外Manacher算法O(n)

重要:字符串Hash不用二分,也能O(n):每次探索len时赋值为当前的ans。char[N]比string快。

8.图论

简单:图的存储(矩阵,邻接表,链式前向星),最短路(BFS)

中等:最短路 最小生成树 拓扑排序二分图匹配 差分约束 无向图的连通性 有向图的连通性 强连通分量 割点 割边 缩点 桥 分数规划 2-SAT 树的直径的重心 LCA 树链剖分 树分块 虚树

困难:网络流*

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值