学习目标:
- 快速排序
- 归并排序
- 二分
- 高精度
- 前缀和
- 差分
- 双指针
- 位运算
- 离散化
学习内容:
快速排序:
- 核心思想:分治、递归
- 核心代码:
`void quick_sort(int q[],int l ,int r)
{
if(l>=r) return;
int x=q[l + r >> 1];
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);
quick_sort(q,j + 1,r);
}`
- 适用范围:数据规模大时。
- 关键点:让right先找,如果left先找相遇位置比x值大不能交换。
归并排序:
- 核心思想:分治
- 核心代码:
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,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++];
for(int i=l,j=0;i<=r;i++,j++) a[i]=tmp[j];
}`
- 适用范围:数据规模大时
二分:
- 适用范围:上下界[a, b]确定、函数在[a, b]内单调。
- 核心代码:
int x;
cin>>x;
int l=0, r=n - 1;
while(l < r)
{
int mid=l+r>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
if(a[l]!=x)
{
cout<<"-1 -1"<<endl;
}
else
{
cout << l << " ";
l=0, r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
cout << l << endl;
}`
- 二分的两种模版:
区间[l,r]被分为[l,mid]和[mid+1,r]时:
int bsearch_1(int l,int r)
{
while(l<r)
{
int mid = l+r>>1;
if(check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
区间被分为[l,mid-1]和[mid,r]时:
int bserach_2(int l,int r)
{
while(l<r)
int mid = l+r+1>>1;
if(check(mid)) l=mid;
else r = mid-1;
}
return l;
}
-
查找第一个>=x的数:
if(data[mid]>=x)
用模版二 -
查找最后一个<=x的数:
if(data[mid]<=x)
用模版一 -
浮点数二分不需要考虑边界问题。
高精度:
- 适用范围:数据过大时
- 核心思想:利用字符串模拟数字计算。
- 加法核心代码:
int t = 0;
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(1);
- 减法:
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;
}
for (int i = 0, t= 0; i < A.size(); i++)
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
- 乘法:
int t=0;
for(int i=0;i<A.size();i++)
{
t+=A[i]*b;
C.push_back(t%10);
t=t/10;
}
if(t) C.push_back(t);
while(C.size()>1&&C.back()==0) C.pop_back();
- 除法:
vector<int>C;
r=0;
for(int i=A.size()-1;i>=0;i--)
{
r=r*10+A[i];
C.push_back(r/b);
r=r%b;
}
reverse(C.begin(),C.end());
while(C.size()>1&&C.back()==0) C.pop_back();
前缀和:
- 适用范围:可将O(n*n)的时间复杂度简化为O(1)
- 核心代码:
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
- 二维前缀和:
核心代码:s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j]
部分前缀和公式:s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
差分:
- 一个长度为n的数组{a1,a2,…an}
构造一个b数组,使ai=b1+b2+…bi;
b1=a1,b2=a2-a1,bn=an-an-1
差分与前缀和互为逆运算 - 适用范围:给定区间[l,r]对该区间内所有数加或减c,可将时间复杂度降为O(1).
O(n)的时间通过b数组还原出a数组 - 核心代码:
int insert( int l, int r, int c){
b[l] = b[l]+c;
b[r+1] = b[r+1] -c;
}
例题
#include<iostream>
using namespace std;
const int N = 1e6+10;
int a[N],b[N];
int insert( int l, int r, int c){
b[l] = b[l]+c;
b[r+1] = b[r+1] -c;
}
int main(){
int n, m;
scanf("%d %d", &n, &m);
int c;
for(int i = 1; i <= n; i++){
scanf("%d", &c);
a[i] = c;
insert( i, i, c);
}
int l, r;
while(m--){
scanf("%d %d %d", &l, &r, &c);
insert( l, r, c);
}
for(int i = 1; i <= n; i++){
a[i] = a[i-1] + b[i];
}
for(int i = 1; i <= n; i++){
printf("%d ", a[i]);
}
return 0;
}
双指针
- 核心代码:
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 每道题目的具体逻辑
}
核心思想:
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
}
}
将上面的朴素算法优化葱O(n*n)到O(n)
- 适用范围;
对于一个序列,用两个指针维护一段区间,比如快排的划分过程
对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作 - 例题:
#include<iostream>
using namespace std;
const int N = 1e6+10;
int a[N],s[N];
int main()
{
int n;
cin>>n;
int res = 0;
for(int i = 0; i < n; i++) cin>>a[i];
for(int i = 0, j = 0; i < n; i++)
{
s[a[i]]++;
while(j <= i && s[a[i]] > 1)
{
s[a[j]]--;
j++;
}
res = max(res, i - j +1);
}
cout<<res;
return 0;
}
位运算
-
给定一个数字n,求n的二进制表示中第k位数字:
先把第k位移到最后一位:n >> k(n从第0位开始)
然后根据个位的数字来确定第k位的数字:x & 1 -
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数:
核心代码:`返回n的最后一位1:lowbit(n) = n & -n -
例题:
#include<iostream>
using namespace std;
int n,x;
int lowbit(int a){
return x&(-x);
}
int main(){
cin>>n;
while(n--){
cin>>x;
int ans=0;
while(x){
x=x-lowbit(x);
ans++;
}
cout<<ans<<" ";
}
return 0;
}
`
离散化
-
适用范围:假设几个数1 50 60000 100000000;如果直接开这么大的数组不现实,所以适用离散化将这些间隔大的点映射到相邻的数组元素中
a[] 1,50,600000,100000000↓,↓, ↓,↓ 1,2,3,4,
-
核心代码:
vector<int> alls; // 存储所有待离散化的值
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, ...n
}
- 例题:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 3e5 + 10;
vector<int> alls;
vector<PII> add, query;
int a[N], s[N];
int n, m;
int find(int 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 l + 1;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; ++i) {
int x, c;
cin >> x >> c;
alls.push_back(x);
add.push_back({x, c});
}
for (int i = 0; i < m; ++i) {
int l, r;
cin >> l >> r;
alls.push_back(l);
alls.push_back(r);
query.push_back({l, r});
}
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
for (auto item : add) {
int x = find(item.first);
a[x] += item.second;
}
for (int i = 1; i <= alls.size(); ++i) s[i] = s[i - 1] + a[i];
for (auto item : query) {
int l = find(item.first);
int r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}