追梦算法----杂乱笔记

1、在局部变量/数组记得初始化数组(否则会乱码,出现各种而样的问题),在全局变量/数组会自动初始化。
2、比较函数:strcmp(char *str1,char *str2),
从第一个字符开始逐字比较两个字符,字符不相等,则函数返回str1[i]-str2[i](可以通过正负值判断大小)。如果两个字符串完全相同,则会返回0。
在c++里面string类型的可以直接判断大小;
3、strlen():获取字符串长度到‘\0’结束。注意不要将strlen()写在for循环的结束条件里,否则可能会增加时间复杂度。
4、字串查找可以用find(起始位置,结束位置,需要寻找的东西);
5、字符数组一定要留一个位置给‘\0’;
6、cin、scanf都是以空格为结束条件,在输入有空格的情况下可以用fgets,fgets(字符数组(s1),输入的最大长度,stdin)是以‘\n’为结束条件;
gets()是以‘\n’为结束条件,并且不会读入‘\n’;
注意:fgets()会读入最后的‘\n’(windows ‘\n’是两个字符 所以需要将strlen()得到的长度-2);
7、使用多个if的时候如果不用else,就会使用完if,可能还会执行下一个if;
8、判断奇偶可以通过判断数字的最后一位来判断奇偶性;
9、使用gets()读入字符串一定要注意‘\n \r’;
10、字典序:是一种排序方法:
对于字符串,先按首字符排序,如果首字符相同,再按第二个字符排序,以此类推。
如aa,ab,ba,bb,bc就是一个字典序。
11、if语句的先后顺序很重要,注意If语句的主次关系!

日期            
假设星期为w,年份为y,月份为m,日期为d。
1、w=(d+2*m+3*(m+1)/5+y+y/4-y/100+y/400)%7可以根据公式将所给日期算出星期几;
把算出来的w加上1就能的到星期几
注意每年的1、2月要当成上一年13、14月计算,上述的除法均为整除。
2、时差需要判断加减;


sort
1、从大到小排序可以使用sort(起始位置,终点位置的下一位,greater<int>());
2、1e-6:(1*10的-6次方);
3、四舍五入函数:round(四舍五入的数); 
4、min()函数一次只能计算两个变量,如果需要计算三个以上的变量可以套用如:min(a,min(b,c));
5、输出的时候注意不要输出行末空格;
6、像成绩排序输出学生的学号的这种排序需要使用结构体进行捆绑然后再排序;bool cmp(结构体的名字 a,结构体定义的名字 b,);
     注意:使用结构体排序的时候,记得把所有的情况都考虑,并排序一下(正常情况下,特殊标注不会相同就不用)如:成绩相同是按学生的首字母排序等;
7、比赛中输入输出都是分开的可以一边输入一边输出;
8、变量初始化的时候使用一个足够小的数(如果最坏的结果是负数的话,初始化零就不对了);
9、定义数组用常量,比题目数据范围稍微大一点;
10、判断浮点数相等应该用极小值eps来辅助,一般eps取1e-8足够了,确保比题目约定的精度误差要求更小。
11、这里inf + inf会溢出,超出了int的范围。可以把inf的定义改成:const int inf = 0x3fffffff,就可以确保不会溢出了。
12、如上的代码可以让dist数组中的所有元素赋值为0x3f3f3f3f,并且两个初始值相加也不会溢出;
13、没有弄清楚操作符优先级。在优先级不确定的情况下,用小括号来明确指定优先级能够避免这类问题的发生。
当然,最好还是要弄清这些符号之间优先级的关系。
14、避免访问非法内存。访问非法内存的事情经常发生,但是可以通过养成好习惯来避免。
比如stack、queue、set访问之前必须先确认不为空;访问指针之前确保指针不是野指针;数组内存开得足够大,等等
15、int值的范围是-2*10^9~2*10^9(2的31次方-1)  long long 范围是9*10^18;
16、在scanf里用*修饰符,是起到过滤读入的作用。比如一个有三列数值的数据,我只想得到第2列数值,
可以在循环里用scanf(“%*d%d%*d”,a[i])来读入第i行的第2个数值到a[i]。 
 * 修饰符在printf中的含义完全不同。如果写成printf(“%6d”, 123),很多同学应该就不会陌生了,这是设置域宽的意思。
同理,%6s也是域宽。* 修饰符正是用来更灵活的控制域宽。
使用%*s,表示这里的具体域宽值由后面的实参决定,如printf(“%*s”,6, “abc”)就是把”abc”放到在域宽为6的空间中右对齐。 
明白了 * 是用变量来控制域宽,那么这题就简单了,这里应该填写5个实参。然后字符长度的计算应该用buf而不是s,因为buf才是截断后的长度,用s的话,如果s长度超过了width-2,效果就不对了

头文件
#include <algorithm>
#include <string.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
using namespace std;

快捷键
ctrl+h 替换;

全排列函数:
next_permutation(起始位置,结束位置);
原理:1、将现有的排列从后往前找,找第一个逆序数(即如果现有排列为123,则第一个逆序数为2)
2、记录第一个逆序数的位置,再从后往前找第一个比逆序数大的数字
3、交换第一个逆序数的值与第一个比逆序数大的数的值
4、将第一个逆序数(不包括此位置)到最后一个数字(包括此位置的数)中的所有数字翻转(利用reverse(初始位置,结束位置));


字符串插入函数:
数组名字 . insert(需要插入的位置,“需要插入的字符串”);


string 定义的字符串结尾为:字符串名字 . length();


字符串截取函数:
数组名字 . substr(截取的起始位置 , 需要截取字符串的长度);

输入
输入的时候注意判断输入有可能是升序也有可能是降序;
某个数的开根号可以使用sqrt()函数也可以是i*i<x;

rand()产生随机数字;
尽量不要用pow()函数;

一定要注意要求的是第15个月还是第14个月,需不需要把本月的数额加上去;

输入数据可能夹杂着回车,导致输出错误

scanf("%[^\n]",a); 表示除了换行符以外的字符全部接收

switch(判断的变量)
{
case '变量的可能取值':
    执行的操作;
case '变量的可能取值':
    执行的操作;……
}

字典序最小一定是升序

记录某数字出现的次数可以用数组来记录;

枚举的时候可以用return 0;直接结束循环枚举


求最大值或者最小值的时候把变量设成无穷小或无穷大的值;

vector(动态的创建数组,头文件 <vector>)与数组类似,多数情况下开在全局变量位置;
1、比如有5万个盒子每个盒子可以最多可以装5万的东西,正常情况下数组是开不了的,此时告诉你,数目的总量维持在2*10的6次
方,所以我们可以动态的创建数组,使得每个盒子能装的最大容量都不同,但总量维持在2*10的6次方。
2、构造一个vector的语句为:vector<类型名>  想要定义的数组名字。 
构造时我们可以给数组初始化:vector<类型名>  想要定义的数组名字+(初始化的长度,初始化的值);
如果第二个参数的值不传的话默认初始化的值为 0 ; 
vector数组也可以开二维的:vector <vector <想要开的数组类型 >此处一定要加一个空格,避免与位运算混乱> +想要开的数组名字
如:vector<vector<int> >vec2;
可以理解为vector里面又装着一个vector即二维数组
注意:二维数组没有初始化的时侯不能直接使用
二维vector初始化:
vector<vector<int> > vec2(n, vector<int >(m,0))
3、插入元素(在数组尾部插入元素):数组名+点+push_back(需要加的内容) 如:vec.push_back(1);
4、想知道vector的长度,可以通过size()方法获取  如vec.size();
注意:vector.size()指的是从0的位置到vector.size()-1的位置,最后vector.size()位置为空;
5、想要修改数组里面的元素直接用 = 进行赋值(注意不要修改,还没有开出来的区域)
6、删除元素使用  数组名+点+pop_back()如vec.pop_back()  删除元素只能在尾部操作;
7、可以用clear()清空vector,(clear()只是清空了vector里面的值,并不会清空开的内存)
用一种方法可以清空内存:
//vector <int> v ;
vector <int>().swap(v);//创建一个新的数组,交换数组里面的内容;
尽量不要在竞赛中使用指针,使用指针容易出现问题。


集合是数学中的一个基本概念,通俗地理解,集合是由一些不重复的数据组成的。比如{1,2,3}就是一个有1,2,3三个元素的集合。
集合函数:set(包含在头文件< set >中); 
1、构造一个set的语句为:set<想要构造的类型>+构造的变量名;初始的时候全为空
如:set<int> aa   set<string> bb;
 2、c++中用insert()函数向集合中插入一个新元素。注意如果集合中已经存在了某个元素,再次插入不会产生任何效果,集合中
是不会出现重复元素的
3、c++中通过erase()函数删除集合中的一个元素,如果集合中不存在这个元素,不进行任何操作。(变量名+点+erase(需要删除的元素))
4、c++中如果你想知道某个元素是否在集合中出现,可以使用count()函数。如果集合中存在我们要查找的元素,返回1,否则返
回 0 ;
5、c++通过迭代器可以访问集合中的每个元素,迭代器就好像一根手指指向set中的某个元素。通过*(解引用运算符,不是乘号的意
思)操作可以获取迭代器指向的元素。通过++操作让迭代器指向下一个元素,同理 - -操作让迭代器指向上一个元素。
迭代器的写法比较固定,set<T>::iterator it 就定义了一个指向set<T>这种集合的迭代器 it,T是任意的数据类型。其中::iterator
是固定的写法。(set<T>::iterator部分不要打空格)
begin函数返回容器中起始于元素的迭代器,end函数返回容器的尾后迭代器(即最后一个元素的下一个位置)。
注意:1、用循环遍历每个元素的时候不要用it<country.end();应该使用 it!=country.end();
    输出的时候迭代器前+*号 如(cout<<*it<<endl;)
          2、在c++中遍历set是从小到大遍历的,也就是说set会帮我们排序的。(如果对结构体要使用set的话需要自定义一下小于号)
6、清空set使用clear(),会清空内存(所定义的结构体名称+点+clear())
7、set经常会配合结构体来使用,用set来储存结构体和vector有些区别。正如我们前面所说的那样,set是需要经过排序的。系统自带
的数据类型有默认的比较大小的规则,而我们自定义的结构体,系统是不可能知道这个结构体比较大小的方式的。
所以我们需要用一种方式来告诉系统怎么比较这个结构体的大小。其中一种方法叫运算符重载,我们需要重新定义小于符号。
8、排列组合从0加到n的和,为2的n次方;


---------------------------------------------------------------------------------------------------
int为2的31次方=1;
蓝桥杯long long 输出时建议写%l64d或者写cout
需要取模一定要每个答案都要取模,有可能会1%1(这是个坑)

栈:
1、用栈的时候一定要判断栈空还是非空;
     可以使用栈空函数:栈的名字+点+empty()  ,当栈为空的时候返回 1 ,可以在前面加个 !判断非空;
2、进栈操作:栈的名字+点+push(进栈的内容);
3、出栈操作:站的名字+点+pop(),没有传参;
     一般的出栈流程:先取栈顶元素:输出s.top(),或将s.top()赋值到其他变量;
        再出栈(s.pop());

函数递归:
1、求最大公约数;f(x,y):y==0时,return x;else return gcd(y,x%y);


dfs(深度优先搜索):
*深度优先搜索和递归的区别:深度优先搜索是一种算法,注重思想;
而递归是一种基于编程语言的实现方式。深度优先搜索可以用递归实现。
ps:我们也同样可以用非递归的方式实现搜索。


因数个数的求法:
将一个数n分解成质因数后,拆成几的几次方几的几次方相乘,那么,所有的指数加一相乘就是因数的个数。
如2的2次方乘以3的3次方就有,(2+1)*(3+1)这么多个因数。
质因数到了15以后就会超过10的16次方了

long long 是2的63次方-1


队列(queue)
在头文件<queue>中
访问队首元素(front) 后端(rear)
入队(push)出队(pop)
判断队列元素是否为空(empty)统计队列元素个数(size)
构造queue的语句:queue<T> q  定义了一个名为q的储存T类型数据的队列
注意:使用front和pop前,要判断队列非空,否则会判断非法访问内存出错。
想要清空队列的内容,可以手动写循环,pop队首。
代码框架:
void bfs(起始点)
{
将起始点放入队列中;
标记起点访问;
while(如果队列不为空)
{
访问队列中队首元素x;
删除队首元素;
for(x 所有相邻点)
{
if(该点未被访问过且合法)
标记该点已经访问过;
将该点加入队列末尾;
}
}
队列为空,广搜结束;
}

一般需要使用结构体struct;
多组数据记得手动清空队列;

递推:
可以考虑最后一次会发生什么情况

杨辉三角是组合数c(n,m)
若i,j从0开始:
第0行为:c(0,0)的可能,只有一种可能;
第一行为:c(1,0),c(1,1)的可能,只有1,1种可能;
第二行为:c(2,0),c(2,1),c(2,2)的可能,分别为1,2,1种可能;
第三行为:c(3,0),c(3,1),c(3,2),c(3,3)的可能,分别为1,3,3,1种可能;

动态规划:
解决求最优解的问题;,在这类问题中,可能会有许多可行解。每一个解都对应于一个值。
动态规划的基本思路:
我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。
动态规划具有相同的填表格式。

动态规划的基本概念:
阶段:(把多阶的问题划分为一个一个阶段)
把所给问题的求解过程恰当地分成若干个相互联系的阶段,以便于求解。过程不同,阶段数就可能不同。描述阶段地变量称为阶段变量,
常用k表示。阶段地划分,一般是根据时间和空间地自然特征来划分,但要便于把问题的过程转发为多阶段决策的过程。
状态:(我们当前到了哪个点)
表示当过程处于某一阶段的某个状态时,可以做出不同的决定,从而确定下一阶段的状态,这种决定称为决策。不同的决策对应着不同的数值,描述决策的变量成决策变量

状态转移方程:(把某一个状态转移到下一个状态对应的式子)
动态规划中本阶段的状态往往是上一阶状态和上一阶段的决策的结果,由第i段的状态f(i),和决策u(i)来确定第i+1
段的状态。状态转移表示为F(i+1)=T(f(i),u(i)),称为状态转移方程。

策略:(当前状态做一个决策到达另一个状态)
各个阶段决策确定后,整个问题的决策序列就构成了一个策略,对每个实际问题,可供悬着的策略有一定范围,称为允许策略集合。
允许策略集合中达到最优效果的策略成为最优策略。

动态规划(一般以递推的形式,核心为状态和状态转移方程,不要忘了边界的处理。)
必须满足最优化原理与无后效性
最优化原理:(即我当前到这里是最优的,才能保证最后的结果是最优的)
一个过程的最优决策具有这样的性质:即无论其初始状态和初始决策如何,其今后诸策略对以第一个决策所形成的状态
作为初始状态的过程而言,必须构成最优策略。也就是说一个最优策略的子策略,也是最优的。

无后效性:(无论怎么到达的这个状态,这个状态对后面的发展是不影响的或者到后面的状态的影响是一样的,后面的也不会影响前面的)
如果某阶段状态给定后,则在这个阶段以后过程的发展不受这个阶段以前各个状态的影响。

我们考虑动态规划(dp)往往是考虑我们当前面临什么状态,他有可能是从哪个状态来的。
注意:边界的处理,否则可能出现运行时错误,访问过界的情况。
对于图中的边界点,要在转移前加上判断是否为边界点,如:点(1,3)只能从点(1,2)走过来,点(3,1)只能从点(2,1)走过来等。
例:给一个n*m的矩阵,每个格子有体力,求回家需要花费的最少体力:
我们就可以把(i,j)这个点看作一种状态,dp(i,j)就代表花费多少体力。
我们从哪个点走到(i,j)就是一种决策
我们往往考虑dp的时候,更多时候是考虑,我们当前面临一个状态,他有可能是从哪来的。(例:dp(i,j)=min(dp(dp(i-1,j),dp(i,j-1)+Aij))
根据状态转移方程,我们可以推出走到每个点花费的最少体力。
总之,你转移的顺序需要保证,你这个点可以接到前面所有该有的点的转移,或者从前往后考虑这个点转移能转移到的所有点你都转移到了,就可以了。
有时候反向解题可能更简洁,快速。

在我们在做多维度的动态规划的时候,我们第一步是先描述事物。一个好的描述等于成功的一半,描述的时候我们先不必管我们使用空间
大小的问题。当我们可以准确的描述清楚一个物体的各个状态的时候,我们再开始考虑优化,看看有没有哪个描述是没有必要的,哪个
描述是可以通过另外的一个值表示的。
我们在做多维动态规划的时候,对比一、二维的动态规划需要转换思维,当维数少的时候,我们可以借助画图的方式来理解。
但是当是多维空间的时候,我们一定要清楚我们数组中的每一维表示的是什么?然后才能写递推方程式

一般动态规划都需要开数组:我怎么知道开一维数组还是开二维数组?
得到了子问题和原问题公共的汉字抄下来,然后看有几个变量,有一个就开一维数组,两个开二维数组。


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值