算法笔记(自用)
时间:2023-01-26 23:00:00
C Basic
C 基础/基本注意点
类型范围
int -2147483648~2147483647
long -2147483648~2147483647
long long -9223372036854775808~9223372036854775807 (别用cin,用scanf(“%lld”)) (2^64)
有时候long long 不能用long double
常用定义
正无穷 0x3f3f3f3f
正无限初始化
使用
头文件:memset(v,0x3f,sizeof v) ///将v值初始化为无限大0x3f
注意:使用memset初始化只能初始化0,-1,0x3f; 如果要初始化其他值,请使用它们
fill()
fill(a,a n,val);
orfill(a.begin(),a.end(),val);
in stl
数据输入输出
-
大规模使用数据 scanf 读取 printf 输出
-
输出固定n位数(不足0)
printf("d",x) ///输出五位整数,不足0
-
处理字符串(带空格)
getline(cin,str); //注意,如果你按在前面,你必须回车getchar()一下!cin.ignore()
连续读取带所需数字的字符串,如
12 is the root of 35
,可以使用sscanf
sscanf(str.c_str(),"%d is the root of %d",&a,&b);
而
sprintf
可以将数字或其他数字写入字符数组char res[5]; sprintf(res,"d",num);
-
小数位保留
直接 printf(“%.xlf”,xxx);在小数点之后,你可以保留几 round(x);作用是四舍五入小数点后的一个,比如 1.5->2.0 ; 1.56->2.0
转义
%
的转义是%%
位运算
-
除2位运算:
>>1
,例如:(a b)/2
写成a b>>1
,a/=2
写成a>>=1
;同理,乘以2就是<<1
(注:只能操作整数) -
例如,不等于-1
i!=-1
的快速写法~i
-
对2取模,简写成x&1 例如:
if(x%2==1)
改写成if(x&1==1)
一些常用的位置操作
- 求n二进制第k位(k=0是个位):
n>>k&1
- lowbit:返回x的最后一个1,应用:求n二进制中的1个数
int lowbit(int x){
return x&-x; //或者x&(~x 1) } int res=0;
while(x){
x-=lowbit(x);
res++;
}
cout<<res;
浮点数的比较方法
由于计算机中浮点数的存储问题,不能直接==
,需要判断他们相减的值是不是小于某个很小的数,加很多括号是为了防止宏定义出错
const double eps = 1e-8;
#define Equ(a, b) ((fabs((a) - (b))) < (eps))
#define More(a, b) (((a) - (b)) > (eps))
#define Less(a, b) (((a) - (b)) < (-eps))
#define LessEqu(a, b) (((a) - (b)) < (eps))
if(Equ(a, b)) {
cout << "true";
}
圆周率 Pi
const double Pi = acos(-1.0);
数组复制
#include
memcpy(a,b,sizeof b); //把b数组复制到a数组中
结构体
结构体可以{aa,bb,cc}
这样写,例如:
struct Person {
string name;
int age,w;
}
vector<Person> ages[N];
int main(){
char name[10];
int age,w;
scanf("%s%d%d",name,&age,&w);
ages[age].push_back({
name,age,w}); //将结构体插入ages容器数组中,可以使用类似pair的写法
}
//结构体中也可以定义方法
struct Student(){
Student(){
} //构造函数
Student(string _id):id(_id){
//带参数的构造函数
for(int i=1;i<=k;i++) grade[i] = -2;
total = cnt = 0;
}
void calc(){
//自定义计算方法
for(int i=1;i<=k;i++){
total += max(0,grade[i]);
if(grade[i]==p_score[i]) cnt++;
}
}
bool has_submit(){
for(....)
....
return false;
}
bool operator< (const Student& t) const {
//运算符重载
.... return ....
}
}
STL 操作
注意事项
xxx
的最后一个元素的迭代指针是xxx.end()-1
- 如果要重复用记得
clear()
一下…
向量 vector
- vector找最值和对应位置,使用中的
min_element()和max_element()
,返回最值的迭代器
auto pos = min_element(v.begin(),v.end())和max_element(v.begin(),v.end())
对应位置是pos-v.begin();值是*pos
-
建立一个逆序vector
vector
B(A.rbegin(),A.rend()) -
遍历vector
vector<Person> ages[N];
for(auto &age:ages) sort(age.begin(),age.end());
-
找一个元素
v.find(key) 返回下标指针
-
去重
unique()
需要和``v.erase()`配合才是真正的去重v.erase(unique(v.begin(),v.end()),v.end());
-
初始化二维vector固定大小
//例如在固定大小的地图中 int row = grid.size(); int col = gird[0].size(); vector<vector<int>> dp(row,vector<int>(col,0));
-
push_back()和emplace_back()
emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。
push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
映射 map
- 查找这个map中有没有这个key:
m.count(key)
unordered_map是没自动排序的map,此时通过max_element()找到的最大值,他的key不一定是最小的,若是map则两个相同的值中,找到的key是最小的。
- 找到Map中最大的value对应的key
使用max_element()
配合cmp()
函数中使用pair组成键值对
bool cmp(const pair<type1,type2> left,const pair<type1,type2> right){
return left.second<right.second;
}
- 对map的排序
对map排序实际上还是要转换在vector中进行,重载比较函数
集合 set
添加
s.insert(key)
删除
s.erase(key或者iterator)
这里参数可以是值也可以是位置指针
注意,在multiset中erase一个数的话会把set里的数全删除,删一个数使用s.erase(s.find(x))
优先队列 priority_queue
定义
priority_queue
使用优先队列实现堆
- 小根堆:
priority_queue
PII是pair经过typedef,greater > heap - 大根堆:
priority_queue
,less > heap
字符串 string
-
删除特定字符
使用
find()
和erase()
-
大小写转换
遍历字符串,对每个字符使用头文件中的
tolower()
,toupper()
-
字符串转数字、数字转字符串
数字 --> 字符串 :
to_string()
字符串 --> 数字:
1. 整数 stoi()2. 浮点,使用 atof(str.c_str()) //这里要传char* 也可以 stof() 和 stod()
-
找到对应子串位置
str.find("substr...");//如果没找到返回-1,找到返回其下标
-
在特定位置插入字串
s.insert(pos,size,char);
例如在字符串开头插入n个0
s.insert(0,n,'0')
-
sscanf()
,sprintf()
从字符串中读入数据和把数据写入字符串中 -
string
和char[]、char*
的转换关系:-
string -> const char*使用
c_str()
-
char*和char[]可以直接转string,如:
string s; char* p="hello"; s=p;
-
-
带空格的字符串按空格分割,使用
stringstream
#include
vector<string> res; //存放分割后字符串string words="ACD BCD CCD"; string word; stringstream input(words); while(input>>word){ res.push_back(word); cout<<word<<endl; }
关于
isalnum(char c)
判断该字符是不是字符数字或者字母,是返非0,否则为0,还有isupper()
,islower()
等
队列queue、栈stack
他们不支持clear()
操作
-
queue::emplace()
将元素插入队列末尾
-
queue::push()
插入队尾
-
queue::pop()
队头出队
关于permutation
-
is_permutation(v1.begin(),v1.end(),v2.begin())
判断v2是不是v1的序列,是1否0 -
next_permutation
和prev_permutation
用法string s = "12345"; do{ cout<<s<<endl; }while(next_permutation(s.begin(),s.end())); string s = "12345"; do{ cout<<s<<endl; }while(prev_permutation(s.begin(),s.end()));
排序
注意要稳定排序有个stable_sort()
1. 默认函数greater()
和less()
sort的排序默认从小到大递增排列,也可以增加第三个参数传入一个比较函数,C++提供两个默认的比较函数
greater<>()
从大到小,递减排列
less<>()
从小到大,递增排列(默认)
注意:在创建map<>
,set<>
等STL类型时,通常可以传入比较函数改变其默认排序
快排模板
void quick_sort(int q[],int l,int r){
if(l>=r) return;
int x = q[l],i=l-1,j=r+1;
while(i<j){
}
}
2. 使用运算符重载
运算符重载可以放到结构体中
//例如在Person结构体中利用运算符重载排序,要求按资产w降序,否则年龄age升序,其次姓名name升序
struct Person{
string name;
int age,w;//年龄和资产
bool operator< (const Person &t) const//重载小于号
{
if(w!=t.w) return w > t.w; //先按资产排序,资产不同则资产大的排在前面
if(age!=t.age) return age < t.age; //否则年龄不同的话,年龄小的排在前面
return name < t.name; //最后姓名字典序小的排在前面
} //注意后面使用 < 的时候,若 a
3. 使用lamda表达式
sort(a,a+n,[](int x,int y){
if(cnt[x]!=cnt[y]) return cnt[x]>cnt[y];
return x<y;
});
//也可以匿名函数
auto cmp = [](int x,int y){
if(cnt[x]!=cnt[y]) return cnt[x]>cnt[y];return x<y;};
4. 手写cmp()函数
bool cmp(const int a,const int b){
//若e[a]
if(e[a]!=e[b]) return e[a]<e[b];
return e[a]>e[b];
}
排序算法操作
K路归并
标准:利用堆找每一路最大/小
其次:使用遍历找
开个vector数组,然后把数组中的vector都排好序,利用遍历找最小
//遍历找最小模板
int t = -1;
for(int i=a,a<=b;i++){
if(t==-1||ages[t][idx[t]]<ages[i][idx[i]])
t=i;
}
//for结束后,t就是最小值的下标
while (true) {
d<<=1;
bool eq = match();
for (int i = 0; i < n; i += d) {
sort(a + i, a + min(n,i + d)); //这里间隔d个排序为什么用min,因为最后面不够d个只能到n了
}
if (eq) break;
}
关于排序题的排名输出(相同分数要求排名一样)
int rank = 1;
for(int i=0;i<list.size();i++){
if(i&&grade[i]!=grad[i-1]) rank=i+1; //如果成绩不等rank就是i+1;
//输出信息...
}
一些题型套路
- 推理题都是用枚举来做
- 链表题都丢到数组里做
基本算法技巧
双指针
一般分两大类,一类是两个指针分别指向两个序列,一类是两个指针指向一个序列
一般都是先把暴力算法写出来,然后看有没有单调性,有就用双指针优化
核心思想
单调性,将双重循环优for(i:n){for(j:n){}}化到O(n)
基本模板
for(int i=0,j=0;i<n;i++){
while(j<n&&check(i,j)) j++; //然后是具体逻辑}
离散化
用于要使用值做下标的情况下,值域非常大,但是个数不多的时候,把序列映射到从0开始的连续自然数
- 若序列中有重复元素则需要去重
- 需要能快速算出序列中的值离散化后的值(二分)【起始也可以排序后用一个unordered_map记录位置】
//1. 把所有要用到的值加入一个数组中
//2. 对数组进行排序去重
all.erase(unique(all.begin(),all.end()),all.end());
//3. 遍历一次记录对应位置关系
unordered_map<int,int> pos;
for(int i=1;i<=all.size();i++) pos[all[i-1]]=i; //如果要用到前缀和就从1开始映射
//4. 之后每次要用到原本的值,就套一个pos[x]
基础算法模板
二分
左闭右闭,找是否存在满足条件的元素,没找到返回-1
int BS(int l,int r,int key){
while(left<=right){
int mid = l+r>>1; //防止溢出可以mid=left+(right-left)/2 if(A[mid]==key) return mid; else if(A[mid]>key) r = mid-1; else l=mid+1; } return -1;}
找第一个满足条件的元素位置【该条件一定是从左到右先不满足,然后满足】
int BS(int l,int r){
while(l<r){
int mid = l+r>>1; if(check(mid)) r=mid; else l=mid+1; } return l;}
找第一个不满足性质的元素位置【从左到右满足,然后不满足】
int BS(int l,int r){
while(i<r){
int mid = l+r+1>>1; if(check(mid)) l=mid; else r=mid-1; } return l;}
DFS、回溯枝剪
深搜关键在于顺序
关于n皇后的技巧性(数组设置)
int row[N],col[N],dg[N],udg[N]; //这里col[i]表示第i行是否有皇后,dg和udg有坐标变换u-i+n和u+i//即dg[x+y]表示该斜是否有皇后,udg[x-y+n]表示另一斜是否有皇后 //dfs 原始思维 O(2^(n^2)) void dfs(int x,int y,int s) //x表示行,y表示列,s是皇后个数 { if(y==n) y=0,x++; //列越界,跳至下一行 if(x==n) { //到达最后一行 if(s==n){ for(int i=0;i<n;i++) puts(g[i]); puts(""); } return; } dfs(x,y+1,s); //不放皇后 if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){ g[x][y] = 'Q'; //记录 row[x] = col[y] = dg[x+y] = udg[x-y+n] = true; dfs(x,y+1,s+1); row