C++算法 常用框架(更新中)

c++常见框架

1.排序

冒泡、插入、选择、桶、归并、快速、希尔排序。默认从小到大排序。

 //冒泡:依次比较相邻的两个数,若前面的大于后面的,则交换这两个数
 ​
 //插入:假设前面的数已经有序,将当前的数插入到前面已经有序的数列中,找到一个合适位置,合适位置之前不动,合适位置之后的都往后移动一个位置。
 ​
 //选择:每次从剩下的数中选择一个最小的放到当前的位置
 ​
 //桶排序:利用数组下标排序:根据数据范围建立桶、统计每个数出现的次数、打印
 ​
 //归并:先分解再合并,对半分开,分解到每个部分只有一个数;合并:将两个有序的序列合并成一个新的有序序列
 ​
 //快速排序:每次选取一个基准数key,让key之前的都比key小,key之后的都比key大,利用插空填数法;分成了两个部分,在每个部分继续快速排序。
 ​
 //希尔排序:设置一个步长d=n,每次间隔d个数进行插入排序,d每次减半直到1。

2.高精度

基本数据类型在计算时精度不够,利用字符串保存数据。

 //+ 对应位置相加,之后处理进位
 ​
 //- 对应位置相减,不够减借位
 ​
 //* c[i+k]+=a[i]*b[k],之后对数组c处理进位
 ​
 // /&%  高进度/低精度,p=p*10+a[i]  p为余数,开始为0

3.二分&分治

二分:把一个问题一分为二,通常用来找一个东西,二分的前提是有序。

分治:把一个问题分解成多个小问题来求解。

 int left=1,right=n;
 while(left<=right){
     int mid=(left+right)/2;
     if(f(mid)==x){
         结果相关
     }
     else if(f(mid)>x){//说明大了,在左边找,改变right
         right=mid-1;
     }
     else{//说明小了,在右边找,改变left
         left=mid+1;
     }
 }
 cout<<left or right相关的;
 //二分 找数 http://noi.openjudge.cn/ch0111/01/
 //二分 求平方根
 //二分 木材加工
 //二分 解方程 http://noi.openjudge.cn/ch0204/7891/
 //二分 填报大学 https://cspoj.com/problem.php?id=1899
 //二分 砍树 https://cspoj.com/problem.php?id=1908
 //分治
 //快速排序
 //归并排序-逆序对
 //循环比赛日程表
 //取余运算
 //黑白棋子的移动
 //麦森数-快速幂

4.递推

递推:由已知推出未知,通常是设数组、找关系、循环求解。

 //斐波那契数列 走楼梯 铺地砖 蜜蜂路线 https://cspoj.com/problem.php?id=1368
 //昆虫繁殖
 //数字三角形
 //位数问题
 //过河卒 https://cspoj.com/problem.php?id=1224
 //流感传染

5.贪心

将大问题分解成多个小问题,每个小问题都求最优解,最终导致大问题的最优解

 //排队打水-接水时间少的先
 //均分纸牌
 //删数问题-递增去最后一个,递减去第一个
 //拦截导弹-有多套系统可以用,用高度最低的
 //活动选择-结束时间早的优先
 //最大整数-大的字符串在前面
 //纪念品分组-大的和小的在一组
 //合并果子(霍夫曼编码)-每次合并两个最小的
 //金银岛-选性价比高的
 //书架-选高度高的奶牛

6.深度优先搜索dfs

基本思想:从起点出发,有多个选择可以选择时,选择其中一个走下去,直到目的地,走不通则返回上一步。

6.1:迷宫问题

 void dfs(int k){
     if(目的地){
         结果相关//打印、找最小值、判断能不能到
     }
     else{
         for(各种选择){
             新的点
             if(条件){ //坐标在合理范围、可以走、没有被走过
                 标记
                 保存结果
                 dfs(k+1)
                 取消标记
             }
         }
     }
 }
 //迷宫问题

6.2:排列问题

 void dfs(int k){
     for(各种选择){
         if(条件){
             标记
             保存结果
             if(目的地){
                 结果相关
             }
             else{
                 dfs(k+1);
             }
             取消标记
         }
     }
 }
 //素数环
 //n选m排列
 //数字拆分
 //八皇后问题
 //工作分配
 //选书
 //马走日
 //有重复元素的排列

7.广度优先搜索bfs

基本思想:从起点出发,从内到外,逐层遍历,直到目的地。第一次找到就是最短的。

队列:先进先出 栈:先进后出

 queue<int> p;
 p.push(a);    //入队,放入队尾
 p.front();    //获取队首元素
 p.pop();      //将队首去掉
 p.back();     //获取队尾
 p.size();     //队列长度
 p.empty();    //判断队列是否为空

bfs 框架

 void bfs(){
     初始化(起点入队)
     while(队列非空){
         p.front();
         p.pop();
         for(各种选择){
             新的点
             if(条件){
                 标记
                 保存结果
                 入队
                 if(目的地){
                     结果相关
                 }
             }
         }
     }
 }
 //迷宫问题 营救 献给阿尔吉侬的花束 仙岛求药
 //红与黑
 //城市路线
 //细胞
 //面积
 //最少转弯问题

8.动态规划

将大问题分解成多个阶段,在每个阶段进行决策,决策依赖于之前的状态,会对之后的决策产生影响,最终结果最优。通常用在max min问题中。

四步走

 1.设数组f(一维、二维、三维)
 2.理解数组的含义
 3.寻找状态转移方程(关键在于i的选择与否)
 4.循环求解
 //数字三角形
 //最长上升子序列 拦截导弹 友好城市 登山 LIS
 //最大上升子序列和
 //最长公共子序列 LCS
 //机器分配
 //挖地雷

8.1最长上升子序列 LIS 不连续--数组

问题描述:找到最长的n个数字中不连续的上升的序列。
in:1 7 3 5 9 4 8	out:4
  
数组设置:f[i]:以第i个数结尾的LIS的长度,f[1]=1
基本思想:去前面找一个高度低中的最大值加一
f[i]=max(f[k])+1		a[k]<a[i],k的范围:1至i-1
结果:max(f[1,...n])

8.2 最大字段和连续--数组

问题描述:n个整数(包含负整数)组成的序列a1,a2,…,an,求该序列子段和的最大值。当所有整数均为负值时定义其最大子段和为0。
in:-2,11,-4,13,-5,-2	out:11-4+13=20
  
数组设置:f[i]:从a1到ai(前i项)中,包含ai的最大的子段和,f[1]=a1
基本思想:前一个f小于0,则为自己。
f[i]=max(f[i-1]+a[i],a[i]) 
结果:max(f[1,...n])

8.3最长公共子序列-矩阵类型

 题目描述:查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。
 in:
 abcdefghijklmnop
 abcsafjklmnopqrstuvw
 out:
 长度为7,(jklmnop)
 ​
 数组设置:f[i][k]:数组a的前i个字符中、数组b的前k个字符中最长公共字符串的长度。f[i][0]=0,f[0][k]=0
 基本思想:相同就向前推一个,不相同就两个里面找个少的。
 f[i][k]=(f[i-1][k-1]+1,f[i-1][k],f[i][k-1]),前一个是a[i]=b[k]的情况,后面两个是不相等的情况。
 结果:f[n][m]

8.4字符串编辑距离-矩阵类型

 题目描述:对于两个不同的字符串,我们有一套操作方法来把他们变得相同,具体方法为:修改一个字符(如把“a”替换为“b”)。删除一个字符(如把“traveling”变为“travelng”)。对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加/减少一个“g”的方式来达到目的。无论增加还是减少“g”,我们都仅仅需要一次操作。我们把这个操作所需要的次数定义为两个字符串的距离。给定任意两个字符串,写出一个算法来计算出他们的距离。
 in:
 3
 abcdefg  abcdef
 ab ab
 mnklj jlknm
 out:
 1 0 4
 ​
 数组设置:f[i][k]将字符串a的前i个字符变成字符串b的前k个字符所需要的最少操作步骤。f[0][k]=k,f[i][0]=i
 基本思想:相同则往前推一个,不相同则在三个方法中选取一个。
 f[i][k]=min{min(f[i-1][k-1],f[i-1][k],f[i][k-1])+1,f[i-1][k-1]},前面是a[i]不等于b[k],后面是a[i]等于b[k]。
 结果:f[n][m]

8.5合并石子-区间类型

 题目描述:在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。计算出将N堆石子合并成一堆的最小得分。
 in: 7 13 7 8 16 21 4 18
 out:239
 ​
 数组设置:s[i]表示前i堆石头的价值总和,f[i][j]表示把第i堆到第j堆的石头合并成一堆的最优值。f[i][i]=0
 基本思想:i到j中间选择一个中介点,合并左边,合并右边,然后再合并左右两部分。
 f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1])
 结果:f[1][n]

8.6乘积最大--区间类型

 题目描述:设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。有一个数字串:312,当N=3,K=1时会有以下两种分法:(1)3*12=36,(2)31*2=62,符合题目要求的结果是:31*2=62。
 in:
 4 2
 1231
 out:
 62
 ​
 数组设置:设f[i][k]表示在前i位数中插入k个乘号所得的最大值,a[j][i]表示从第j位到第i位所组成的自然数。用f[i][k]存储阶段k的每一个状态。f[j][0]=a[1][j] (1<j<=i)。
 f[i][k]=max{f[j][k-1]*a[j+1][i]}(k<=j<=i)
 结果:f[n][k]

8.7乘法游戏--区间问题

题目描述:乘法游戏是在一行牌上进行的。每一张牌包括了一个正整数。在每一个移动中,玩家拿出一张牌,得分是用它的数字乘以它左边和右边的数,所以不允许拿第1张和最后1张牌。最后一次移动后,这里只剩下两张牌。你的目标是使得分的和最小。
例如,如果数是10 1 50 20 5,依次拿1、20、50,总分是                       10*1*50+50*20*5+10*50*5=8000,而拿50、20、1,总分是1*50*20+1*20*5+10*1*5=1150。 
in:
6
10 1 50 50 20 5
out:3650

数组设置:f[i][j]表示将ai~j的数全部取走(包括ai和aj)后所获得的最小得分,第0位和第n-1位不能取走,故f[1][n-2]即为所求。
f[i][k]=min(f[i][k],f[i][j-1]+f[j+1][k]+a[i-1]*a[j]*a[k+1])

9.背包问题

//01背包:每个物品只有一个,取或不取。前i个。
//完全背包:每个物品有无限个。前i种。
//多重背包:每个物品有若干个
//混合背包:前三种的混合
//二维费用的背包:多了一个限制
//分组背包:同一组的物品冲突。前i组。
//有依赖关系的背包:省略。
//背包问题的方案总数:max-->sum

9.1 01背包

 题目描述:有N件物品和一个容量为V的背包。第i件物品的费用(即体积)是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
   
 特点:每种物品仅有一件,可以选择放或不放。
 ​
 数组设置:f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值
 基本思想:第i件物品选择放或者不放
 状态转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+c[i]}
 二维数组的解法:
 for (int i = 1; i <= n; i++)            // f[i][v]表示前i件物品,总重量不超过v的最优价值
     for (int v = m; v > 0; v--)
         if (w[i] <= v)  f[i][v] = max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
         else  f[i][v] = f[i-1][v];
 printf("%d",f[n][m]);                   // f[n][m]为最优解
 一维数组的解法,容量一定要从大到小
 for (int i=1; i <= n; i++)              //设f(v)表示重量不超过v公斤的最大价值
     for (int v = m; v >= w[i]; v--)
         if (f[v-w[i]]+c[i]>f[v])
             f[v] = f[v-w[i]]+c[i];
 printf("%d",f[m]);                      // f(m)为最优解
 ​

9.2 完全背包

 题目描述:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。 
 特点:每种物品都有无限件可用
 数组设置:f[i][v]表示前i种物品恰放入一个容量为v的背包可以获得的最大价值
 状态转移方程:f[i][v]=max{f[i-1][v-k*w[i]]+k*c[i]|0<=k*w[i]<= v}
 状态转移方程:f[i][v]=max(f[i][v-w[i]]+c[i],f[i-1][v]),f[n][m]即为最优解
 容量一定是从小到大:
 for i=1..N 
     for v=0..V
         f[v]=max{f[v],f[v-w[i]]+c[i]}; 

9.3 多重背包问题

 题目描述:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
 特点:每种物品有多个
 思想:第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。
 状态转移方程:f[i][v]=max{f[i-1][v-k*w[i]]+ k*c[i]|0<=k<=n[i]}。
 for (int i = 1; i <= n; i++)
     for (int j = m; j >= 0; j--)
         for (int k = 0; k <= s[i]; k++){
             if (j-k*v[i]<0) break;
                 f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
             }

9.4 混合背包问题

 题目描述:如果将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。
 for (int i = 1; i <= n; i++)
     if (p[i] == 0){                           //完全背包
         for (int j = w[i]; j <= m; j++)
             f[j] = max(f[j], f[j-w[i]]+c[i]);
     }
 else{
     for (int j = 1; j <= p[i]; j++)           //01背包和多重背包
         for (int k = m; k >= w[i]; k--)
             f[k] = max(f[k],f[k-w[i]]+c[i]);
 }

9.5 二维费用的背包问题

题目描述:二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价a和代价b,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为c[i]。
数组设置:f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。
状态转移方程:f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+c[i]}
相当于01背包,也会有完全背包的情况。

9.6 分组的背包问题

题目描述:有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是c[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本思想:选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有
f[k][v]=max{f[k-1][v],f[k-1][v-w[i]]+c[i]|物品i属于第k组}
for 所有的组k
		for v=V..0
				for 所有的i属于组k
						f[v]=max{f[v],f[v-w[i]]+c[i]}
注意这里的三层循环的顺序,“for v=V..0”这一层循环必须在“for 所有的i属于组k”之外。这样才能保证每一组内的物品最多只有一个会被添加到背包中。

10.数论相关

10.1 欧几里得算法--最大公因数

专门算两个数的最大公约数的算法,又称辗转相除法。即 gcd(a,b) = gcd(b,a%b)。很明显,边界是b = 0,返回a。
gcd(int a,int b) {
    return b == 0 ? a : gcd(b,a % b);
}

10.2 唯一分解定理

任何一个正整数,都可以表示(分解)为所有素数(素数又叫质数,是因数只有两个的数)各种幂的积(1 = 任何素数 ^ 0,任何素数 = 自己 ^ 1),并且每个不同的数都只有一种分解,所以叫唯一分解定理。若一个数依次不被比自己小的素数整除,就可以判断它是素数。两个数的最大公约数就是这两个数的唯一分解式中重合的因子的积。一对互质数的分解中是没有重复的。

如 36 = 2 ^ 2 * 3 ^ 2 ( 2 * 2 * 3 * 3 ),   10 = 2 * 5。

10.3 埃氏筛

 题目描述:想要知道小于等于n有多少个素数
 基本思想:对于任意一个大于 1 的正整数 n,那么它的 x 倍就是合数(x > 1)。利用这个结论,我们可以避免很多次不必要的检测。如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数。时间复杂度是 O(n(log(logn)))。
   
 vector<int> prime;
 bool is_prime[N];
 ​
 void Eratosthenes(int n) {
   is_prime[0] = is_prime[1] = false;
   for (int i = 2; i <= n; ++i) is_prime[i] = true;
   for (int i = 2; i <= n; ++i) {
     if (is_prime[i]) {
       prime.push_back(i);
       if ((long long)i * i > n) continue;
       for (int j = i * i; j <= n; j += i)
         // 因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i
         // 的倍数开始,提高了运行速度
         is_prime[j] = false;  // 是 i 的倍数的均不是素数
     }
   }
 }

10.4 线性筛

 埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。如果能让每个合数都只被标记一次,那么时间复杂度就可以降到O(n)了。线性筛法 也称为 Euler 筛法(欧拉筛法)
 vector<int> pri;
 bool not_prime[N];
 ​
 void pre(int n) {
   for (int i = 2; i <= n; ++i) {
     if (!not_prime[i]) {
       pri.push_back(i);
     }
     for (int pri_j : pri) {
       if (i * pri_j > n) break;
       not_prime[i * pri_j] = true;
       if (i % pri_j == 0) {
         // i % pri_j == 0
         // 换言之,i 之前被 pri_j 筛过了
         // 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被
         // pri_j 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break
         // 掉就好了
         break;
       }
     }
   }
 }
 ​
 int pri[N+9>>1],now;
 bool vis[N+9];
 void init(){
     for(int i=2;i<=N;i++){
         if(!vis[i])pri[++now]=i;
         for(int j=1;j<=now&&pri[j]*i<=N;j++){
             vis[pri[j]*i]=1;
             if(i%pri[j]==0)break;
         }
     }
 }

11.Dikstra

12.Belman-Ford

13.Spfa单源次短路

14.Floyd求任意两点最短路和传递闭包

并查集;

最小生成树: prim,kruskal;

树的三种遍历;

RMQ问题:

ST算法,

树状数组。

树与二叉树的转化、

树的最长链

最近公共祖先

欧拉道路和欧拉回路

二分图及相关算法;

求强连通分量

强连通分量的缩点算法

求割边和割点

树形DP;

数论进阶:同余式,逆元,中国剩余定理,扩展欧几里得

高级动态规划与高级数据结构专题:

斜率优化,状态压缩;

字符串专题:KMP算法,Manacher算法,Trie

树数据结构:线段树,平衡树

网络端管法

动态村二维

线段域

块状链:AC自动动机

KP

复杂动态规划点型

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值