acwing算法基础课学习笔记
Acwing算法基础课,看了快一年了,我好菜哦。
up-to-star
做个努力善良的人
展开
-
贪心——区间问题
贪心就感觉是一个目光很短的人看问题,每次都在一个有限的区域内找到最优解,然后再往后看,贪心也没有具体的套路,所以就把看课程的几道题总结一下。区间选点问题描述给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。输出选择的点的最小数量。位于区间端点上的点也算作区间内。分析首先将每个区间按右端点从小到大排序,然后遍历所有区间,每次都很贪心的取区间的右端点,如果下一个区间已经覆盖了前一个区间的右端点,就跳过。算法的正确性可以通过模拟示例进原创 2021-07-20 09:57:33 · 209 阅读 · 0 评论 -
动态规划——树形DP&记忆化搜索
树形DP树形DP就是在一棵树上进行动态规划,根据一个节点的孩子树递归获取信息,然后进行状态转移。没有上司的舞会题目描述Ural 大学有 NNN 名职员,编号为 1∼N1 \sim N1∼N 。他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。每个职员有一个快乐指数,用整数 HiH_{i}Hi 给出, 其中 1≤i≤N1 \leq i \leq N1≤i≤N 。现在要召开一场周年庆宴会, 不过, 没有职员愿意和直接上司一起参会。在满足这个条件的前提下,主办方希望邀请一部分职原创 2021-07-17 09:29:21 · 290 阅读 · 1 评论 -
动态规划——状态压缩DP
我所理解的状态压缩DP就是将所有状态用二进制数表示,然后根据前一个合法的二进制数表示的状态进行状态转移,得到最终答案。蒙德里安的梦想题目描述求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。分析这道题的核心是要发现所有的情况之和横着放的小矩形的情况有关。当所有横着放的矩形确定以后,竖着放的小矩形只能从剩余的空间中塞进去。所以可以考虑每一列,在每一列中有横着放或者前一列中横着放原创 2021-07-16 23:24:10 · 200 阅读 · 1 评论 -
动态规划——线性DP&区间DP
线性动态规划数字三角形描述给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。分析代码#include<bits/stdc++.h>using namespace std;const int N = 510,INF = -1e9;int f[N][N],s[N][N];int n;int main(){ scanf("%d",&原创 2021-07-13 10:52:01 · 199 阅读 · 0 评论 -
动态规划——背包问题
01背包问题问题描述有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。分析关键是用前边的状态来更新当前状态,就是将一个问题分解为子问题,由子问题的最优解来得出当前问题的最优解。同时还有根据实际意义确定边界值#include<bits/stdc++.h>using namespace std;const int N =原创 2021-07-12 10:13:11 · 377 阅读 · 0 评论 -
数学知识——容斥原理与简单博弈论
容斥原理百度百科的定义:在计数时,必须注意没有重复,没有遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。以模板题为例,在取不同集合时,可以用二进制位是否为1来表示,是1就取该位,为0就不取,而二进制恰好可以表示所有组合的情况,就很妙。#include<bits/stdc++.h>using n原创 2021-07-10 16:26:17 · 174 阅读 · 0 评论 -
数学知识——求组合数
就是求CabC_{a}^{b}Cab1、预处理所有C[a][b],用公式Cab=Ca−1b+Ca−1b−1C_{a}^{b} =C_{a-1}^{b}+C_{a-1}^{b-1}Cab=Ca−1b+Ca−1b−1当a,b数据范围较小时适用。long long C[N][N];int mod = 1e9+7;void zuhe(int n){ for(int i=0;i<=n;i++){ for(int j=0;j<=i;j++){ if(!j) C[i][j]原创 2021-07-09 13:06:28 · 280 阅读 · 0 评论 -
数学知识——欧拉函数、快速幂、扩展欧几里得算法
欧拉函数欧拉函数定义为ϕ(n)=1−n中与n互质的个数\phi(n)=1-n中与n互质的个数ϕ(n)=1−n中与n互质的个数,互质就是最大公约数是1。欧拉函数求解公式:将n分解质因数:n=p1a1+p2a2+...+pkakn=p_1^{a1}+p_2^{a2}+...+p_k^{ak}n=p1a1+p2a2+...+pkak,则ϕ(n)=n∗(1−1p1)∗(1−1p2)∗.....∗(1−1pk)\phi(n)=n*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*原创 2021-07-08 11:32:18 · 398 阅读 · 0 评论 -
数论——质数、约数等
质数大于2的数只包含1和它本身两个约数的数是质数。判断质数1、最朴素做法——试除法bool is_prime(int x){ for(int i=2;i<n;i++) if(x%i==0) return false; return true;}//时间复杂度是O(n)2、优化到sqrt(n)我们可以发现,如果一个数可以整除d则也可以整除n/d,所以可以进行优化bool is_prime(int x){ for(int i=2;i<=x/i;i++){ i原创 2021-07-07 12:06:39 · 216 阅读 · 0 评论 -
二分图——染色法、匈牙利算法
二分图的定义二分图就是图中的点可以分成两部分,在每部分内部不存在边,边只存在在两部分之间。二分图的一个重要性质:当且仅当图内部不存在奇数环,则可以为二分图。因此可以根据此性质,用染色法,将每个节点和其相连的点染成不同的颜色,如果在染色过程中发生矛盾,则就不能二分。模板题代码:#include<bits/stdc++.h>using namespace std;const int N = 2e5+10;int h[N],e[N],ne[N],idx;int color[N];i原创 2021-07-06 14:49:54 · 188 阅读 · 0 评论 -
最小生成树问题——Kruskal算法
算法思路将每条边按权值排序枚举每条边的两个点和权值,如果a,b不连通,将这条边加入集合中。(是初始时不连通,逐步加到集合中并连同,用并查集实现)模板题代码:#include<bits/stdc++.h>using namespace std;const int N = 2e5+10;int p[N];int n,m;struct Edge{ int a,b,c; bool operator<(Edge x){//关键字排序,重载运算符原创 2021-07-06 11:24:00 · 160 阅读 · 0 评论 -
最小生成树问题——Prim算法
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。Prim算法有朴素版的和堆优化的两种,堆优化的算法一般不常用,可以用Kruskal算法代替。朴素Prim算法的思想设定一个集合,来存储已经确定的点将每个点到集合的距离初识化为无穷大在集合外找到到集合距离最小的点,用这个点更新其他点到集合的距离。迭代n次。模板题代码:#include<bits/stdc++.h>using namespace std;const原创 2021-07-06 10:41:31 · 193 阅读 · 0 评论 -
多源汇最短路问题——Floyd算法
Floyd算法是基于动态规划的思想,定义一个数组d[N][N],d[x][y]表示x,y之间的最短路问题,经过三重循环最终得到结果。时间复杂度O(n3)O(n^3)O(n3)代码如下#include<bits/stdc++.h>using namespace std;const int N = 210,INF=1e9;int d[N][N];int n,m,k;void floyd(){//三重循环的floyd算法 for(int k=1;k<=n;k++)原创 2021-07-06 09:13:24 · 208 阅读 · 2 评论 -
最短路问题——spfa算法
spfa算法是对bellman-ford算法的优化。bellman-ford算法更新时有些更新是无效的,就造成了时间上的浪费,因此,spfa就用一个队列存储更新了的点,用更新的点更新后边的点。就是说,只有前边一个点变小了,后边的点才可能变小。操作步骤:1节点入队从队头取出一个元素,用该元素更新后边的结点,如果后边的节点是有效更新,就入队,重复这个操作。spfa求最短路#include<bits/stdc++.h>using namespace std;const int N原创 2021-07-05 23:41:17 · 182 阅读 · 0 评论 -
最短路问题——Bellman-Ford算法
Bellma-Ford算法用来处理有负权边的最短路问题,其操作步骤如下:循环n次遍历所有边a,b,w;松弛操作:dis[b] = min(dis[b],dis[a]+w)由于是直接遍历所有边,所以在图的存储时可直接用结构体存。struct greaph{ int a,b,w;}模板题#include<bits/stdc++.h>using namespace std;const int N =510,M = 1e4+10;int dis[N],backup[N];原创 2021-07-05 21:42:24 · 232 阅读 · 0 评论 -
最短路问题——Dijkstra
最短路问题有下面两种:单源最短路问题就是从一个点出发,到其他点的最短路。所有边权都是正值的:朴素版的Dijkstra算法(一般用于稠密图),时间复复杂度是O(n2)O(n^2)O(n2);堆优化的Dijkstra算法(用于稀疏图),时间复杂度是O(nm)O(nm)O(nm)。存在负权边:Bellman-Ford算法,时间复杂度是O(nm)O(nm)O(nm);SPFA算法,时间复杂度是O(m)O(m)O(m)多源汇最短路就是多个起点到终点的最短路问题Floyd算法,时间复杂度是O(n3原创 2021-07-05 15:26:38 · 270 阅读 · 0 评论 -
拓补序列
定义拓补序列是针对有向图而言的:若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。有向无环图一定存在拓补序列,所以有向无环图也称作拓补图。入度与出度入度是指一个点有多少条边指向它出度是指有多少条边从它出发所有入度为零的点都可以排在当前最前面的位置。操作步骤将所有入度为零的点入队从队头取一个元素,遍历所有该元素的边,删除该元素指向下一元素的边如果下一个元素的入度为零,就入队。模板题有向图的拓扑序列原创 2021-07-05 11:24:18 · 701 阅读 · 0 评论 -
树与图的遍历
树与图的存储树是一种特殊的图,树是连通的有向无环图。图有节点和边组成。图存储一般由两种方法:邻接矩阵,就是用一个二维数组g[a][b]存储a和b之间的一条边。多用于存储稠密图。邻接表。就是开一个链表数组,每个链表存储该节点可以到达的结点。//h[N]为链表数组的头指针数组h[N],e[N*2],ne[N*2],idx;void insert(int a,int b){ e[idx] = b; ne[idx] = h[a]; h[a] = idx++;}树与图的遍历1.深度优原创 2021-07-05 10:37:26 · 167 阅读 · 0 评论 -
dfs与bfs
深度优先搜索(dfs)深度优先搜索顾名思义就是按照深度一条路走到黑然后再回溯,搜下一条路。这里要理解搜索树的概念,深度优先搜索的一条路都对应搜索树的一个枝儿。要注意,每次回溯时要恢复现场。全排列问题#include<bits/stdc++.h>using namespace std;const int N = 10;int path[N],n;bool st[N];void dfs(int u){ if(u==n){ for(int i=0;i<n;i++)prin原创 2021-07-04 21:11:15 · 318 阅读 · 0 评论 -
hash表
hash表是将数据映射到下标,常用的有两种方法:拉链法和开放寻址法拉链法拉链法是利用邻接表,将冲突的元素存储在一个链表中,其中h数组就是一个所有链表的头指针数组。具体实现如下:#include<bits/stdc++.h>using namespace std;const int N = 1e5+3;//一般用一个质数,这样冲突的概率小;int h[N],e[N],ne[N];int idx;void Insert(int x){ int k = (x%N+N)%N;原创 2021-07-04 08:52:54 · 93 阅读 · 0 评论 -
堆与堆排序
堆堆是一个完全二叉树,完全二叉树就是二叉树除了最后一层外均为满的,最后一层从有向左排列。堆分为小根堆和大根堆。小根堆的根节点小于左右两个节点,大根堆相反。堆的相关操作down操作void down(int u){ int t = u; if(u*2<=size&&h[t]>h[u*2])t = u*2; if(u*2+1<=size&&h[t]>h[u*2+1])t = u*2+1; if(t!=u){ swap(h原创 2021-07-03 16:58:55 · 113 阅读 · 0 评论 -
并查集
并查集可以在近乎O(1)O(1)O(1)的时间复杂度内实现两个有用的操作判断一个元素是否属于同一个集合将两个集合合并基本原理每个集合用一棵树来表示,树根的编号就是整个集合的编号,每个元素都指向它的父节点,p[x]表示x的父节点。判断树根:x==p[x]求x集合的编号:while(p[x]!=x) x = p[x];合并两个集合:p[x] = y;裸并查集#include<bits/stdc++.h>using namespace std;const int N原创 2021-07-03 15:33:14 · 66 阅读 · 0 评论 -
Trie树(字典树)
Trie树用于快速查找某个字符是否存在在。就是用一棵树存储所有字符,有一个根节点,从根节点出发寻找字符中的字母是否存在。实现快速查找。具体代码实现以模板题为例字符串统计#include<bits/stdc++.h>using namespace std;const int N = 3e5+10;int son[N][26];int cnt[N];int idx,n;void insert(string s){ int p = 0;//0表示根节点 for(原创 2021-07-03 10:43:20 · 76 阅读 · 0 评论 -
KMP 算法
KMP算法是字符匹配的算法关键是理解前缀表,即next数组。具体过程就不画了,忘了就去b站找找。next数组构造方法void creatne(char *s,int *next){ int n = len(s); int j = -1; next[0] = j; for(int i=1;i<n;i++){ while(j>=0&&s[i]!=s[j+1]) j = next[j]; if(s[i]==s[j+1]) j++; next[i] = j;.原创 2021-07-01 17:50:26 · 79 阅读 · 0 评论 -
数据结构(一)链表、栈、队列等
单链表如果用结构体表示节点,如下struct LinkNode{ Elem val; LinkNode *next;};用结构体建立单链表时,比较耗费时间采用数组模拟单链表,即静态链表,时间效率较高。e[N],ne[N]分别存储节点的值和下一个节点的下标。head //头指针idx //索引节点的下标初始化操作void Init(){ head = -1;//head指向空 idx = 0;}头插法void insert_head(int x){ e[idx] =原创 2021-07-01 16:52:12 · 135 阅读 · 0 评论 -
双指针算法、位运算等
双指针算法双指针算法就是利用两个指针将o(n2n^2n2)的算法进行优化。基本模板如下:for(int i=0,j=0;i<n;i++){ while(j<i&&check(i,j))j++; //算法的内容。}例如:最长连续子序列问题for(int i=0,j=0;i<n;i++){ b[a[i]]++; while(b[a[i]]>1){//通过j指针使相同的数变成只有a[i]. b[a[j]]--; j++; }}..原创 2021-07-01 11:43:59 · 97 阅读 · 0 评论 -
基础算法(二)前缀和、差分
前缀和定义原数组:a1,a2,...ana_1,a_2,...a_na1,a2,...an前缀和数组:S1,S2...SnS_1,S_2...S_nS1,S2...SnSi=a1+a2+a3+...+aiS_i = a_1+a_2+a_3+...+a_iSi=a1+a2+a3+...+ai前缀和数组主要用来求某个区间的和前缀和的原数组和前缀和数组一般从1开始;s[i] = s[i-1] + a[i];sum(l,r) = s[r] - s[l-1];//主要的语句。二原创 2021-06-30 19:56:08 · 146 阅读 · 0 评论 -
基础算法(一)
快速排序快速排序的基本思想是分治寻找一个分界点,一般是左端点或右端点或中间值或随机一个数。进行调整,调整后的数大于等于轴的在轴的右边,小于等于轴的在左边。递归处理左右两段。具体做法:两个指针,一个从前往后遍历,有个从后往前遍历,前边那个找到第一个比轴大的停下来,右边那个找到第一个比轴小的停下啦,交换,当i>=j时停止。void quick_sort(int *a,int l,int r){ if(l>=j) return; int v = a[l+r>>1];原创 2021-06-30 16:09:09 · 90 阅读 · 0 评论
分享