第一讲 基础算法

学习方法

  • 课上:学习算法思想,明白这个算法为什么是对的,听课的时候跟着敲一遍代码,学完一个知识点之后,再独立敲一遍
  • 课后:跟着博客进行复习
    读入规模较大的时候,使用scanf
    ——积跬步,至千里;积小流,成江海。

快速排序

算法思想:基于分治

  1. 确定分界点
  2. 调整范围,使得分界点的左边元素都比其小,分界点的右边元素都比其大
  3. 递归处理左右两端

分治算法

题目链接:785. 快速排序

#include<iostream>
using namespace std;
const int N = 100005;
int q[N];
void quick_sort(int q[], int l, int r){
    if(l >= r){
        return;
    }
    int i = l - 1, j = r + 1, x = q[(l + r) >> 1];
    while(i < j){
        //从左往右,找到大于x的数字
        do{
            i ++;
        }while(q[i] < x);
        
        //从右往左,找到小于x的数字
        do{
            j --;
        }while(q[j] > x);
        
        //将大于x的元素和小于x的元素进行交换
        if(i < j){
            int t = q[i];
            q[i] = q[j];
            q[j] = t;
        }
    }
    //x左边的元素都小于等于它,x右边的元素都大于等于它
    //递归处理x左右两边
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++){
        scanf("%d", &q[i]);
    }
    quick_sort(q, 0, n - 1);
    for(int i = 0; i < n; i ++){
        printf("%d ", q[i]);
    }
    return 0;
}

题目链接:786. 第k个数

利用快速排序的思想解题,每次划分,只要递归包含第k个数的那部分,时间复杂度为O (n)

归并排序

在这里插入图片描述

787. 归并排序 - Acwing题库

归并排序也是基于分治法:

  • 分解:将数列分成左右两半
  • 解决:递归排序左边和右边,分解到不能再分解
  • 合并:把每次分开的两部分合并到一起
#include<iostream>
using namespace std;
const int N = 100005;
int q[N], tmp[N];//tmp为辅助数组
void merge_sort(int q[], int l, int r){
    if(l >= r){//如果区间个数为1个或者没有的话,直接返回
        return;
    }
    
    int mid = (l + r) >> 1; //获取分界点
    
    //递归排左右两边
    merge_sort(q, l, mid); merge_sort(q, mid + 1, r);
    
    int k = 0, i = l, j = mid + 1;
    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(){
    int n;
    scanf("%d", &n);
    
    for(int i = 0; i < n; i ++){
        scanf("%d", &q[i]);
    }
    
    merge_sort(q, 0, n - 1);
    
    //输出
    for(int i = 0; i < n; i ++){
        printf("%d ", q[i]);
    }
    return 0;
}

题目链接:788. 逆序对的数量 - Acwing题库

二分

整数二分:

二分的本质其实是“边界”,例如,左边红色区域可以满足某个条件,右边绿色区域不可以满足某个条件,注意两个区间是没有交集的,因为这是整数二分,那么二分就是找到这个边界,那么既可以是红色区域的最右边那个位置,也可以是绿色区域最左边那个位置,这就对应着接下来将的两个模版。
二分一定有解,无界是题目中涉及的情况,可以特判。
在这里插入图片描述

题目链接:789. 数的范围

#include<iostream>
using namespace std;
const int N = 100005;
int q[N];
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    
    for(int i = 0; i < n; i ++){
        cin >> q[i];
    }
    
    while(m--){
        int x;
        scanf("%d", &x);
        //先递归满足条件的最左边
        int l = 0, r = n - 1;
        while(l < r){
            int mid = (l + r) >> 1;
            if(q[mid] >= x){// 边界的位置在左半边
                r = mid;// 这种更新条件, mid不需要补1
            }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 = 1 + (l + r) >> 1;
                if(q[mid] <= x){
                    l = mid;
                }else{
                    r = mid - 1;
                }
            }
            cout << l <<' ' << endl;
        }
    }
    return 0;
}

实数二分

题目链接:790. 数的三次方根

#include<iostream>
using namespace std;
int main(){
    double n;
    cin >> n;
    double l = -100, r = 100;// 三次方根的范围
    while(r - l > 1e-8){// 当区间长度小于一个很小的数字的时候就得到结果
        double mid =(l + r) / 2;
        if(mid * mid * mid >= n){
            r = mid;
        }else{
            l = mid;
        }
    }
    printf("%.6lf\n", l);
    return 0;
}

高精度

高精度情况分类

  • A+B:一个大整数加上一个大整数 1 0 6 10^6 106
  • A-B:一个大整数-一个大整数 1 0 6 10^6 106
  • A*b:一个大整数 * 一个小整数

高精度加法

题目链接:791. 高精度加法

#include<iostream>
#include<vector>
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 /= 10;
    }
    if(t){
        C.push_back(1);
    }
    return C;
}
int main(){
    string a, b;
    vector<int>A, B;
    cin >> a >> b;// a = "123456"
    for(int i = a.size() - 1; i >= 0; i--){ //A = [6, 5, 4, 3, 2, 1]
        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);
    for(int i = c.size() - 1; i >= 0; i --){
        printf("%d", c[i]);
    }
    return 0;
}

高精度减法

题目链接:792. 高精度减法

#include<iostream>
#include<vector>
using namespace std;
//判断是否有 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;
}

//C = A - B
vector<int> sub(vector<int> &A, vector<int> &B){
    vector<int> C;
    for(int i = 0, t = 0; i < A.size(); i++){//已经判断过A和B的大小,所以A.size() >= B.size()
        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;
        }
    }
    //去掉前导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;
    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)){
        auto  C = sub(A, B);
        for(int i = C.size() - 1; i >= 0; i--){
            printf("%d", C[i]);
        }
    }else{
        auto C = sub(B, A);
        printf("-");//-(B - A)
        for(int i = C.size() - 1; i >= 0; i--){
            printf("%d", C[i]);
        }
    }
    return 0;
}

高精度乘法

题目链接:793. 高精度乘法

#include<iostream>
#include<vector>
using namespace std;
vector<int> mul(vector<int> &A, int b){
    vector<int> C;
    int t = 0;// 第零位进位为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;
    }
    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');
    }
    if(b == 0){
        printf("0");
    }else{
        auto C = mul(A, b);
    
        for(int i = C.size() - 1; i >= 0; i --){
            printf("%d", C[i]);
        }
    }
    
    return 0;
}

高精度除法

题目链接:794. 高精度除法

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// A / b, 商是c,余数是r
vector<int> div(vector<int> A, int b, int &r){//r 是余数,用引用传递
    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 %= 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;
    cout << r << endl;
    return 0;
}

1h: 18min

前缀和和差分

一维前缀和

数组元素(这里规定下标从1开始,a[0] = 0,下文解释): a 1 + a 2 + a 3 . . . + a n a_1 + a_2 + a_3 ...+ a_n a1+a2+a3...+an

前缀和公式: S i S_i Si = a 1 + a 2 + a 3 . . . + a i ​ a_1 + a_2 + a_3 ...+ a_i​ a1+a2+a3...+ai

如何求 S i S_i Si

s[0] = 0;//下标从1开始,a[0] = 0;
for(int i = 1; i <= n; i++){
    s[i] = s[i - 1] + a[i];
}

前缀和的作用

例如:计算 a l + a l + 1 + a l + 2 . . . + a r a_l+ a_ {l + 1}+ a_{l + 2} ...+ a_{r} al+al+1+al+2...+ar,就是求集合中[l, r]元素的和

使用前缀和的解决方式:s[r] - s[l -1] =( a 1 + a 2 + a 3 . . . + a r a_1 + a_2 + a_3 ...+ a_r a1+a2+a3...+ar)- ( a 1 + a 2 + a 3 . . . + a l a_1 + a_2 + a_3 ...+ a_l a1+a2+a3...+al)= a l + a l + 1 + a l + 2 . . . + a r a_l+ a_ {l + 1}+ a_{l + 2} ...+ a_{r} al+al+1+al+2...+ar

这样做的优点在于提高了速度,如果从l遍历到n进行计算,时间复杂度为O(n),而使用前缀和的方式,时间复杂度为O(1)

那么解释一下为什么元素下标从1开始呢,是为了处理边界 。由a[0] = 0,可以得出s[0] = 0。如果想要计算[1, 10],则计算s[10] - s[0],这里s[0] = 0,所以在边界情况就不需要特判了。

题目链接:前缀和

#include<iostream>
using namespace std;
const int N = 100005;
int a[N];
int s[N];
int main(){
    int n, m, l, r;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> a[i];//读入数组元素
        s[i] =a[i] + s[i - 1];//更新前缀和数组
    }
    for(int i = 1; i <= m; i++){
        cin >> l >> r;
        cout << s[r] - s[l - 1] <<endl;//求【l,r】元素的和
    }
    return 0;
}

二维前缀和

二维前缀和是用来在一个矩阵中求子矩阵的和。例如我们用s[3][3]表示下面这个绿色区域的元素总和。

image.png

(x1, y1) (x2, y2)这一子矩阵中所有数的和如何计算

二维前缀和公式推导.jpg

s[i][j]如何计算

用二维数组a存储矩阵中每一个元素,可以用以下方式计算s[i][j]

QQ图片20240728113101.jpg

题目链接:子矩阵的和

#include<iostream>
using namespace std;
int a[1005][1005];
int s[1005][1005];
int main(){
    int n, m, q;
    int x1, y1, x2, y2;
    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] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }
    while(q --){
        cin >> x1 >> y1 >> x2 >> y2;
        printf("%d\n",s[x2][y2] - s[x1 -1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
    }
    return 0;
}

一维差分

《算法竞赛》P65-67

题目链接:797. 差分 - Acwing题库

#include<iostream>
using namespace std;
const int N = 100010;
int a[N], b[N];
void insert(int l, int r, int c){
    b[l] += c;
    b[r + 1] -= c;
}
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
    }
    for(int i = 1; i <=n; 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] << " ";
    }
    
    
    return 0;
}

二维差分

a[i][j]是b[i][j](没有被表示出来)的前缀和
在这里插入图片描述

现在的目的就是在中间的子矩阵中加上一个c,如何做呢,可以按照下述四步。
在这里插入图片描述

#include <iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N], b[N][N];

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;
}

int main()
{
    scanf("%d%d%d", &n, &m, &q);

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &a[i][j]);

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; 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];


    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ ) printf("%d ", b[i][j]);
        puts("");
    }

    return 0;
}

区间合并

803. 区间合并 - Acwing题库
解题思路:
输入的所有区间,看其中能否合并,能合并的则合并,合并之后算作一个区间,最后看总共包含几个区间。就是要思考如何快速的进行区间合并。
合并:

  • 排序: 区间按照起点进行排序,确保可以按顺序处理。
  • 合并逻辑:
    • st 表示当前区间的起点,ed 表示当前区间的终点。
    • 如果当前区间的起点大于当前正在合并的区间的终点 (ed < seg.first),则将之前的区间加入到结果中,并开始一个新的区间。否则,两个区间有重叠,此时更新终点为两个区间的最大终点 (ed = max(ed, seg.second))。
    • 最后,将合并后的区间保存回 segs。
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
//这里定义了一个 PII 类型,表示一个包含两个整数的 pair(对),这两个整数表示区间的起点和终点
typedef pair<int, int> PII;

void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);

    if (st != -2e9) res.push_back({st, ed});

    segs = res;
}

int main()
{
    int n;
    scanf("%d", &n);

    vector<PII> segs;
    for (int i = 0; i < n; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        segs.push_back({l, r});
    }

    merge(segs);

    cout << segs.size() << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值