基础算法模板(一)

目录

一、快速排序

二、归并排序

三、整数二分 

四、浮点数二分

五、高精度加法

六、高精度减法

七、高精度乘法(大数乘小数)

八、高精度除法(大数除小数)

九、一维前缀和

十、二维前缀和 

十一、一维差分

十二、二维差分

十三、双指针算法

十四、位运算

十五、离散化


一、快速排序

思路:快排就是选择一个哨兵x,然后使得x左边都小于x,x右边都大于x。

怎么实现呢?

方法是双指针法,左边来一个指针i(其实不是真的指向地址的那个指针),右边来一个指针j

然后使i从左往右扫,直到q[i]>=x,停止扫描

使j从右往左扫,直到q[j]<=x,停止扫描

若i,j未相遇,则交换q[i]与q[j]

题目链接:快速排序

模板如下:

#include <iostream>

using namespace std;

const int N  = 1e6 + 10;
int n;
int q[N];

void quick_sort(int q[], int l, int r){
    if(l >= r) return;
    int i = l - 1, j = r + 1;
    int x = q[l + 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);
}
int main() {
    cin >> n;
    for(int i=0; i<n; i++){
        cin >> q[i];
    }
    quick_sort(q, 0, n-1);
    for(int i=0; i<n; i++){
        cout << q[i] << " ";
    }
    cout << endl;
    return 0;
}

二、归并排序

思路:二分区间,分为[l, mid],[mid+1, r]

然后,用两个指针i和j分别指向两个区间序列的第一位,即l和mid+1

然后,合并两个区间,利用tmp数组一个一个按照q[i]<=q[j]的条件去读进tmp数组中

然后,将末尾没比较完的数加到tmp的尾部

最后,把tmp中的数还给q中

题目链接:归并排序

#include <iostream>

using namespace std;

const int N  = 1e6 + 10;
int n;
int q[N], tmp[N];

void merge_sort(int q[], int l, int r){
    if(l >= r) return;
    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while(i <= mid && j <= r){
        if(q[i] < q[j]) tmp[k++] = q[i++];
        else tmp[k++] = q[j++];
    }
    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
    for(int i = l, j = 0; i <= r; i++, j++){
        q[i] = tmp[j];
    }
}
int main() {
    cin >> n;
    for(int i=0; i<n; i++){
        cin >> q[i];
    }
    merge_sort(q, 0, n-1);
    for(int i=0; i<n; i++){
        cout << q[i] << " ";
    }
    cout << endl;
    return 0;
}

归并排序涉及到了逆序对数量的问题。

因为区间[l, mid]和[mid+1, r]都是有序且递增的,所以如果[mid+1, r]这个区间中第j个数小于了[l, mid]这个区间的第i个数,那么显然从i开始一直到mid都大于j,这样就构成了mid-i+1个逆序对,为所求。

题目链接:逆序对数量

代码如下:

#include <iostream>

using namespace std;

typedef long long LL;
const int N = 1e6 + 10;

int q[N], tmp[N];
int n;

LL merge_sort(int q[], int l, int r) {
    if (l >= r) return 0;
    int mid = l + r >> 1;//中位数
    LL res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) {
        if (q[i] <= q[j]) tmp[k++] = q[i++];
        else {
            tmp[k++] = q[j++];
            res += mid - i + 1;
        }
    }
    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];
    for (i = l, k = 0; i <= r; i++, k++) {
        q[i] = tmp[k];
    }
    return res;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> q[i];
    LL res = merge_sort(q, 0, n - 1);
    cout << res << endl;
    return 0;
}

三、整数二分 

思路:其中①为满足左边红色性质,找红色右端点,②为满足右边绿色性质,找绿色左端点。

注意:查找左半边时mid需要+1!

 模板如下:

bool check(int x);

//左半边满足check性质
int bsearch_1(int l, int r){
    while(l < r){
        int mid = l + r + 1 >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

//右半边满足check性质
int bsearch_2(int l, int r){
    while(l < r){
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

整数二分涉及数的范围的问题,可以先用二分查左端点(保证右边的数都>=x),再用二分查右端点(保证左边的数都<=x) ,即可。

题目链接:数的范围

代码如下:

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int q[N];

int main() {
    int n, m;
    cin >> n >> m;
    for(int i=0;i<n;i++) cin >> q[i];
    while(m--){//m个查询
        int x;//查找的数
        cin >> x;
        //先查左端点
        int l=0, r =n-1;
        while(l<r){
            int mid = l+r >> 1;
            if(q[mid]>=x) r = mid;
            else l = mid + 1;
        }
        if(q[l]!=x) cout << "-1 -1" << endl;
        else{
            cout <<l << " ";
            //再查右端点
            int l=0, r =n-1;
            while(l<r){
                int mid = l+r+1 >> 1;
                if(q[mid]<=x) l = mid;
                else r = mid - 1;
            }
            cout << l <<endl;
        }
    }
    return 0;
}

四、浮点数二分

思路:和整数二分基本相同,但是不用考虑边界问题了,很简单

以下代码为利用二分法求数的平方根

#include <iostream>

using namespace std;
//利用二分法求数的平方根
int main(){
    double x;
    cin >> x;
    double l, r;
    if(x >= 1){//此时平方根介于0-x
        l = 0, r = x;
        while(r - l > 1e-8){
            double mid = (l + r) / 2;
            if(mid * mid > x) r = mid;
            else l = mid;
        }
    }
    else{//此时平方根介于x-1
        l = x, r = 1;
        while(r - l > 1e-8){
            double mid = (l + r) / 2;
            if(mid * mid > x) r = mid;
            else l = mid;
        }
    }
    printf("%.6lf\n", l);
    return 0;
}

其中,r-l>=比最后要求保留的小数位数多两位即可! (经验)

五、高精度加法

#include <iostream>
#include <vector>
#include <string>

using namespace std;
//C = A + B
vector<int> add(vector<int> &A, vector<int> &B) {//引用速度更快,不用读入整个数组
    vector<int> C;
    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 = t / 10;
    }
    if (t) C.push_back(1);//如果最后还有进位,得在最高位前再加1
    return C;
}

int main() {
    string a, b;
    cin >> a >> b;
    vector<int> A, B;
    //倒序存入vector,最低位在a[0]
    for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
    auto C = add(A, B);//auto:编译器会自动推断是什么类型的
    for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
    cout << endl;
    return 0;
}

六、高精度减法

#include <iostream>
#include <vector>
#include <string>

using namespace std;

//此函数判断是否有 A >= B
bool cmp(vector<int> A, vector<int> B) {//此时两个数都是倒序存储,数组第0位是最低位,而我们要从最高位开始比
    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;//两数相等
}

//C = A - B
vector<int> sub(vector<int> &A, vector<int> &B) {//引用速度更快,不用读入整个数组
    vector<int> C;
    int t = 0;//借位(暂时当结果)
    for (int i = 0; i < A.size(); i++) {//A肯定比B大
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);//如果t<0,t += 10,反之则t = t
        //借位,0或者1
        if (t < 0) t = 1;
        else t = 0;
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back();//去除前导零
    return C;
}

int main() {
    string a, b;
    cin >> a >> b;
    vector<int> A, B;
    //倒序存入vector,最低位在a[0]
    for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
    if (cmp(A, B)) {//A > B
        auto C = sub(A, B);//auto:编译器会自动推断是什么类型的
        for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
    } else {//A < B
        auto C = sub(B, A);
        cout << "-";//负号
        for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
    }
    cout << endl;
    return 0;
}

七、高精度乘法(大数乘小数)

#include <iostream>
#include <vector>
#include <string>

using namespace std;

vector<int> mul(vector<int> &A, int b) {
    vector<int> C;
    int t = 0;//进位
    for (int i = 0; i < A.size() || t; i++) {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    while(C.size() > 1 && C.back() == 0) C.pop_back();//去除前导零
    return C;
}

//高精度乘法(大数乘小数)
int main() {
    string a;
    int b;
    cin >> a >> b;
    vector<int> A;
    for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
    auto C = mul(A, b);//A × b
    for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
    cout << endl;
    return 0;
}

八、高精度除法(大数除小数)

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

vector<int> div(vector<int> &A, int b, int &r) {
    vector<int> C;//商
    r = 0;
    for (int i = A.size() - 1; i >= 0; i--) {
        r = 10 * r + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());//反转
    while (C.size() > 1 && C.back() == 0) C.pop_back();//去除前导零
    return C;
}

int main() {
    string a;
    int b;
    cin >> a >> b;
    vector<int> A;
    for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
    int r;//余数
    auto C = div(A, b, r);
    for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
    cout << endl << r << endl;
    return 0;
}

九、一维前缀和

有一个序列a[N]:a[1],a[2],a[3]...a[i]...

前缀和s[i]就是序列a[]的前i项的和。

如何求s[i]?

s[i] = s[i-1] + a[i],s[0]=0即可。

注:求前缀和时,a[i]、s[i]都是从1开始的,a[0]、s[0]都为0,可以通过在main函数外赋全局变量的方式初始化第零位。

作用?

可以O(1)地求得序列中一段l到r的部分和,即为res = s[r] - s[l-1]。

题目链接:前缀和

#include <iostream>

using namespace std;
const int N = 1e6 + 10;

int a[N], s[N];//全局变量a[0],s[0]自动为0

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];//前缀和的初始化
    while (m--) {
        int l, r;
        scanf("%d %d", &l, &r);
        printf("%d\n", s[r] - s[l - 1]);//区间和的计算
    }
    return 0;
}

十、二维前缀和 

与一维前缀和类似,仅将a[N]、s[N]改为二位矩阵即可。

如何求s[i][j]?

s[i][j] = s[i-1][j] + s[i][j-1] -s[i-1][j-1] + a[i][j]。

如何求(x1, y1)到(x2, y2)之间的二维前缀和呢?

res = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]

题目链接:子矩阵的和

#include <iostream>

using namespace std;

const int N = 1e3 + 10;
int a[N][N], s[N][N];

int main() {
    ios::sync_with_stdio(false);
    int n, m, q;
    cin >> n >> m >> q;
    //输入
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin >> a[i][j];
        }
    }
    //计算s[i][j]
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
        }
    }
    //求前缀和
    while(q--){
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        int res = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
        cout << res << endl;
    }
    return 0;
}

十一、一维差分

 则a为b的前缀和数组,b为a的差分数组(与前缀和是个相反的概念)。

经常,我们需要对a数组的[l,r]区间每个数都加c,常规做法的时间复杂度是O(n)的baolibian,但是如果用了差分数组,可以达到O(1)。只需要bl+c,b(r+1)-c即可!

对插入为insert(i,i, a[i])的解释:

题目链接:差分 

#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int a[N], b[N];

void insert(int l, int r, int c){
    b[l] += c;
    b[r+1] -= c;
}
int main() {
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    for(int i=1;i<=n;i++) {
        cin >> a[i];
        insert(i, i, a[i]);//对差分数组赋值
    }
    while(m--){
        int l, r, c;
        cin >> l >> r >> c;
        insert(l, r, c);//对差分数组操作
    }
    for(int i=1;i<=n;i++){
        b[i] += b[i-1];//求前缀和
        cout << b[i] << " ";
    }
    cout << endl;
    return 0;
}

十二、二维差分

与一维差分类似,a[N][N]是b[N][N]的前缀和序列,反过来b是a的差分序列,如果我们需要对a中左上角为(x1, y1),右下角为(x2, y2)的这部分数全部加上c,则可以直接对其差分序列b进行O(1)的操作:b[x1][y1] +=c, b[x1][y2+1] -= c, b[x2+1][y1] -= 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]

题目链接:差分矩阵

#include <iostream>

using namespace std;

const int N = 1e3 + 10;
int a[N][N], b[N][N];

void insert(int x1, int y1, int x2, int y2, int c) {
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
}

int main() {
    ios::sync_with_stdio(false);
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            insert(i, j, i, j, a[i][j]);//构建差分数组
        }
    }
    while (q--) {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);//对差分数组操作
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];//求二维前缀和
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

十三、双指针算法

 双指针算法的优势是把O(n^2)的双重for循环优化成O(n),关键是找单调性

先看一道题,很巧妙地用双指针算法清除了前面的数字。

题目链接:最长连续不重复子序列

AC代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
int a[N], s[N];

int main() {
    ios::sync_with_stdio(false);
    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 << endl;
    return 0;
}

 再看一题,巧妙地找到了单调性,利用双指针算法优化了时间复杂度。

题目链接:数组元素的目标和

a,b数组有序,i从a数组的头开始,j从b数组的尾开始,这就满足了单调性。

AC代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
int a[N], b[N];

int main() {
    ios::sync_with_stdio(false);
    int n, m, x;
    cin >> n >> m >> x;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < m; i++) cin >> b[i];
    for (int i = 0, j = m - 1; i < n; i++) {
        while (a[i] + b[j] > x && j >= 0)j--;//根据单调性,j左移
        if (a[i] + b[j] == x) {
            cout << i << " " << j << endl;
            break;
        }
    }
    return 0;
}

十四、位运算

主要介绍了lowbit运算,即为 return x & (-x),结果为x中最低位的1和更低位的所有0。

比如,x=(10101000)2,而~ x=(01010111)2,所以~ x+1=(01011000)2,x&(~ x+1)=(1000)2。

因此,如果不断地让x减去lowbit(x),直到x=0,则减去的次数即为x二进制表示中1的个数。

题目链接:二进制中1的个数

AC代码如下:
 

#include <bits/stdc++.h>

using namespace std;

int lowbit(int x){
    x = x & (-x);
    return x;
}

int main() {
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    while(n--){
        int x;
        cin >> x;
        int cnt = 0;
        while(x){
            x -= lowbit(x);
            cnt++;
        }
        cout << cnt << " ";
    }
    cout << endl;
    return 0;
}

十五、离散化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值