基础算法__习题——排序、二分、前缀与差分

前言

重学算法第1天,希望能坚持打卡不间断.
直到学完提高课。(暂定)
预计时长三个月内,明天再来!肝就完了

2月13日,day01 打卡

学完y总的算法基础课1.3-Week1 习题课

共7题,知识点如下

快排:第k个数
归并排序:逆序对的数量
二分:数的三次方根
前缀和(一维)
子矩阵的和(二维)
差分(一维)
差分矩阵(二维)

快速排序

AcWing 786. 第k个数

思路:
基础算法__习题——排序、二分、前缀与差分_第1张图片

时间复杂度O(n)

#include 

using namespace std;

const int N = 100010;
// 局部变量和全部变量重名时,会优先使用局部变量,因此可以重名
int n, k;
int q[N];

int quick_sort(int l ,int r, int k) {
    if (l >= r) return q[l];
    int x = q[l], i = l - 1, j = r + 1; // 取分界点
    while (i < j) {
        while (q[ ++ i] < x);
        while (q[ -- j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    int sl = j - l + 1;
    if (k <= sl) return quick_sort(l, j, k);

    return quick_sort(j + 1, r, k - sl);
}

int main {
    cin >> n >> k;

    for (int i = 0; i < n; i++) cin >> q[i];

    cout << quick_sort(0, n - 1, k) << endl;

    return 0;
}

归并排序

AcWing 788. 逆序对的数量

思路:
基础算法__习题——排序、二分、前缀与差分_第2张图片

逆序对的最大数量会爆int

基础算法__习题——排序、二分、前缀与差分_第3张图片

#include 

using namespace std;

typedef long long LL;

//const int N = 10010;
const int N = 1e5 + 10;

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

LL merge_sort(int l, int r) {
	if (l >= r) return 0;

    int mid = l + r >> 1;
    LL res = merge_sort(l , mid) + merge_sort(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 ++];
            res += mid - i + 1;
        }
    }
	// 扫尾
    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];

    return res;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)  cin >> q[i];

    cout << merge_sort(0, n - 1) << endl;

    return 0;
}

报了Segmentation Fault
只需要将const int N = 10010;(漏写了0,数组过小)
改为const int N = 1e5 + 10;

二分

AcWing 790. 数的三次方根

0-1有bug
例如:0到0.01不能在中间二分,开根号是0.1,
基础算法__习题——排序、二分、前缀与差分_第4张图片

思路:基础算法__习题——排序、二分、前缀与差分_第5张图片

在左边就把右边界 r 更新为mid
在右边就把左边界 l 更新为mid

保留6位小数
写while时要比有效数后面的位置多2比较保险,即1e-8

#include ;

using namespace std;

int main () {
    double x;
    cin >> x;

    double l = -10000, r = 10000;
    while (r - l > 1e-8) {
        double mid = (l+r)/2;
        if (mid * mid * mid >= x) r = mid;
        else l = mid;
    }
	// 打印l或者r都能ac
    printf("%lf\n", l); // %lf默认6位
    return 0;
}

前缀和与差分

AcWing 795. 前缀和

一维的都很简单

思路:
基础算法__习题——排序、二分、前缀与差分_第6张图片

下标有i-1的都从i=1开始
所以为了防止越界,数组都多定义了10个


#include 

using namespace std;

const int N = 100010;

int n, m;
int a[N], s[N];

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)  cin >> a[i];

    // 求前缀和数组
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];

    while (m --) {
        int l, r;
        cin >> l >> r;
        cout << s[r] - s[l - 1] << endl;
    }
    return 0;
}

AcWing 796. 子矩阵的和

首先要搞懂Sij的含义,左上点是(0,0),往右边和下边开始数,图中绿色就是S34

子矩阵的计算公式
s[x2][y2] = s[x2][y2] - s[x2][y1-1] -s[x1-1][y2] + s[x1-1][y1-1]
公式含义:总的 减去上面部分和左边部分,黑色区域减了2次,需要加回来

s[i,j]计算公式
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
公式含义:上侧加左侧,深蓝区域加了两次要减掉,最后加上a[i,j] (自己那个点)

#include 

using namespace std;

const int N = 1010;

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

int main() {
    scanf("%d%d%d", &n, &m, &q); // 读入规模较大,建议使用scanf来读
    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++) {
            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;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        // 多打了s[x2][y2] =
        // printf("%d\n", s[x2][y2] = s[x2][y2] - s[x2][y1-1] -s[x1-1][y2] + s[x1-1][y1-1]);
        printf("%d\n", s[x2][y2] - s[x2][y1-1] -s[x1-1][y2] + s[x1-1][y1-1]);
    }
    
    return 0;
}

AcWing 797. 差分

一维差分

基础算法__习题——排序、二分、前缀与差分_第7张图片
基础算法__习题——排序、二分、前缀与差分_第8张图片

将O(n)操作降到了O(1)

前缀和有两个核心操作
差分只有一个

#include 

using namespace std;

const int N = 100010;

int n, m; // n:数组长度 m:操作个数
int a[N], b[N]; // a:原数组 b差分数组 

void insert(int l, int r, int c) {
    b[l] += c;
    b[r+1] -= c;
}

int main() {
    // 读取原数组
    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++) a[i] = a[i - 1] + b[i];
    
    for (int i = 1; i <= n; i++) printf("%d ", a[i]);

    return 0;
}

AcWing 798. 差分矩阵

二维差分
只考虑核心操作即可
基础算法__习题——排序、二分、前缀与差分_第9张图片

式子含义:
将b[x1,y1]右下侧整个区域前缀和都加c
再把右侧和下侧的减去C
黑色区域减了2次,加回来
最后黄色区域就是前缀和差分完的区域

基础算法__习题——排序、二分、前缀与差分_第10张图片

#include 

using namespace std;

const int N = 1010;

int n, m, q; // n m:边长, q:操作个数
int a[N][N], b[N][N]; //a原数组 b差分数组,二维数组,不能写掉了

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() {
	// 输入输出较多,用scanf读
    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;
        scanf("%d%d%d%d%d", &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++) {
            a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + b[i][j];
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
          printf("%d ", a[i][j]);
        }
        //puts(""); 与下面换行等同
        cout << endl;
    } 
    return 0;
}

核心公式: a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + b[i][j];

跟前缀和公式一样的
其实for循环有好多步可以合在一起写
但刚学的时候,分开写看的更清楚一些,以后熟练了再合起来

你可能感兴趣的:(#,acwing算法基础课,算法,学习,c++)