基础算法
排序
快速排序(可以是稳定的)
思路
左端为 l ,右端为 r
- 首先确定分界点:q[l] q[r] q[(l+r)/2]都行 (用(l+r)/2 更快一点)
- 然后调整区间:使左部分满足都是≤分界点的,右部分都是≥分界点的
- 最后递归处理左右两端
模板代码
void quick_sort(int q[], int l, int r)
{
if (l >= r)
return; //不满足条件记得return;
int x = q[(l+r)/2];
int i = l - 1, j = r + 1;
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j)
swap(q[i], q[j]);
}
quick_sort(q, l, j); //注意这里右边界如果是j, 上面的x不能是r或者(l+r+1)/2;否则会死循环
quick_sort(q, j + 1, r);
}
归并排序(排序是稳定的)
思路
- 首先确定分界点 mid = (l+r)/2
- 然后递归排序
- 最后归并(两个有序的序列合二为一)
代码模板
void merge_sort(int a[], int l, int r)
{
if (l >= r)
return;
int mid = l + r >> 1; //先确定分界点
merge_sort(a, l, mid); //递归排序
merge_sort(a, mid + 1, r);
//最后归并
int k = 0; int i = l, j = mid+1;
while (i <= mid && j <= r)
{
if (a[i] <= a[j])
tmp[++k] = a[i++];
else
tmp[++k] = a[j++];
}
while (i <= mid)
{
tmp[++k] = a[i++];
}
while (j <= r)
{
tmp[++k] = a[j++];
}//每次要更新a数组
for (i = l, j = 1; i <= r, j <= k; i++, j++)
{
a[i] = tmp[j];
}
}
二分(核心是边界问题)
整数二分
1.要找第一个满足的
int bseach_1(int l,int r)
{
while(l<r)
{
int mid = l+r>>1; //下取整
if(check(mid))
r=mid;
else
l=mid+1;
}
return l;
}
2.找最后一个满足的
int bseach_2(int l,int r)
{
while(l<r)
{
int mid = l+r+1>>1; //注意要上取整
if(check(mid))
l=mid;
else
r=mid-1;
}
}
浮点数二分
不需要考虑边界+1问题
//表示方式一:
for(int i = 1;i<=100;i++)
{
double mid = (l + r) / 2;
if (mid * mid * mid >= n)
r = mid;
else
l = mid;
}
//表示方式二:
while(r-l>1e-8) //要比题目的精度再精确两位
{
double mid = (l + r) / 2;
if (mid * mid * mid >= n)
r = mid;
else
l = mid;
}
高精度
- 输入用string数组来表示
- 倒置变为数字存入vector数组
- 结果存入vector< int >数组
高精度加法
vector<int> add(vector<int>& a, vector<int>& b)
{
vector<int> C;
int t = 0; //t表示进位
for (int i = 0; i < a.size() || i < b.size(); i++)
{
if (i < a.size())
t += a[i];
if (i < b.size())
t += b[i];
C.push_back(t % 10);
t /= 10;
}
if (t)
C.push_back(t);
return C;
}
高精度减法
//高精度减法核心函数
vector<int> sub(vector<int>& a, vector<int>& b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < a.size(); i++)
{
t = a[i] - t;
if (i < b.size())
t -= b[i];
C.push_back((t + 10) % 10); //t此时就是该位上的最终结果
//注意+10 %10 的技巧
if (t < 0)
t = 1;
else
t = 0;
}
while (C.size() > 1 && C.back()==0) //去掉前导零
{
C.pop_back();
}
return C;
}
//判断A,B大小
bool cmp(vector<int>& a, vector<int>& b)
{
if (a.size() != b.size())
return a.size() > b.size();
for (int i = a.size() - 1; i >= 0; i--)
if (a[i] != b[i])
return a[i] > b[i];
return true;
}
高精度乘法
- 第i位和第j位相乘,结果在第i+j-1位
//关键步骤
//ans[0]存的是s1+s2的位数,所以一定大于等于最终结果的位数,需要去除前导零
for (int i = 1; i <= len1; i++)
{
for (int j = 1; j <= len2; j++)
{
ans[i + j - 1] += a[i] * b[j];
ans[i + j] += ans[i + j - 1] / 10;
ans[i + j - 1] %= 10;
}
}
//注意特判
if(s1=="0"||s2=="0")
{
cout<<0<<"\n";
return 0;
}
前缀和
- 一次运算算出前缀的和
一维
//求前缀和
s[i] = s[i-1]+a[i];
//求部分和
S = s[r]-s[l-1];
二维
//求前缀和
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
//求部分和
S = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
差分
- 简单的说就是,对差分的前缀和就是原数组
- 用O(1)的时间给连续区间加上固定的值
一维差分
//求差分(初始化)
b[i] = a[i]-a[i-1];
//或者
b[i]+=a[i];
b[i+1]-=a[i];
//应用
// l,r,c 代表在a数组中l到r区间每个元素增加c
b[l]+=c;
b[r+1]-=c;
- 为了方便插入修改,可以写一个函数
void insert(int l,int r,int c)
{
b[l]+=c;
b[r+1]-=c;
}
//初始化即
insert(i,i,a[i]);
//求前缀和
b[i]+=b[i-1];
二维差分
void insert(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
//初始化即
insert(i,j,i,j,a[i][j]);
//求前缀和
b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
双指针
- 核心思想:把O(N*N)的算法优化到O(N);
代码框架
//只是个大体框架,不一定写的一模一样
for(i = 0,j = 0;i<n;i++)
{
while(j<i && check())
{
j++;
}
//每道题目的具体逻辑
}
位运算
运算符
& 与运算(有一个为0,即为0)
0 & 0 = 0;
1 & 0 = 0;
0 & 1 = 0;
1 & 1 = 1;
| 或运算(有一个为1,即为1)
0 | 0 = 0;
0 | 1 = 1;
1 | 0 = 1;
1 | 1 = 1;
^ 异或(两个数不同,就为1,否则是0)
0 ^ 0 = 0;
1 ^ 1 = 0;
1 ^ 0 = 1;
0 ^ 1 = 1;
常用操作
看n用二进制表示,第k位数字是几(从个位往上数到k)
n >> k & 1
即,先右移k位,再取出此时的个位
lowbit(x)
:返回x的最后一位1
- 注意:
lowbit(x)
中 x 是十进制数,即该函数自动把十进制转化成二进制来运算,并返回其对应的十进制数 - 运算规则:
若 x 的二进制为 1010, lowbit(x) 所对应的二进制为 10;
若 x 的二进制为 101000, lowbit(x) 所对应的二进制为 1000;
- 内部实现
//x & -x 或者 x & (~x+1);
int lowbit(int x)
{
return x & -x;
}
- 可以求一个数的二进制中 1 的个数
while(x)
{
x -= lowbit(x);
res++; //记录二进制中1 的个数
}
离散化
- 特指整数的离散化
概念:
在值域很大,但是元素较少的情况下,有时我们需要用到元素的值作为下标来运算,显然,由于值域很大,开一个数组并用元素值作为下标并不显示,所以我们考虑对其元素值进行映射,映射成一组连续的数值作为下标,即离散化的过程。
实现:
- 先想办法把要离散化的点用数组 alls 存起来(目的是为了后续去重后得到要进行离散化的数组大小,进而方便求前缀和),
- 同时用适当的数据结构(such as pair)来存储题目中进行的操作(目的是为了记录 后期需要离散化的对象,进而对其离散化)
vector<int> alls; //a表示待离散化数组
- 然后对a数组排序、去重
sort(alls.begin(),alls.end());//排序
alls.erase(unique(alls.begin(),alls.end()),alls.end()); //删除重复元素
- 再用二分来求需要离散化对象的映射
//即用二分来求出x对应的离散化的值
int find(int x)//找到第一个大于等于x 的位置
{
int l = 0,r = alls.size()-1;
while(l<r)
{
int mid = l+r>>1;
if(alls[mid] >= x)
r=mid;
else
l=mid+1;
}
return r+1; //映射到1,2,3……n
}
//如何调用?(例如):
int x = find(items.first); //x表示离散化后的坐标
a[x] += c; //a数组表示对离散化后的坐标进行操作