数据结构与算法(AcWing),持续更新中

学习笔记,仅供参考。
如有错误,欢迎探讨。

To Me

1.例题说明

1.整篇笔记的例题都是AcWing上的。

2.例题模块的第一行为例题题号名称,随后的注释为题目内容,再下面是正确的可以运行的代码。

2.模板说明

1.y总给的模板,或者有我自己的一些修改,但是是正确的。

2.都是伪代码。

3.做题细节总结

1.2分中求mid的时候要用mid = l + (r - l) / 2来做。

2.当输入字符的时候可以用字符数组来代替,因为输入有的时候会有空格,而%c严格读入一个字符,所以有可能把空格读入,导致输入结果不对。%s可以过滤掉空格和回车。

3.memset是以字节赋值的,取数的后八位二进制数,然后把数组里所有数的每个字节都赋值成这样。一般为0或-1。

4.让cin和scanf一样快的方法

cin.tie(0);
ios::sync_with_stdio(false);

5.INT_MAIX一般为0x3f3f3f3f。

一、基础算法

排序

1.快速排序——分治

1.找分界点,一般为数组最左边,最右边,中间,或者随机点。

2.使分界点左边的数都小于等于分界点,右边的数都大于等于分界点。

3.递归

模板:

void quick_sort(int q[], int l, int r)
{
   
    if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j)
{
   
    do i ++ ; while (q[i] < x);
    do j -- ; while (q[j] > x);
    if (i < j) swap(q[i], q[j]);
    else break;
}
	quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

例题:

785 快速排序
//给定你一个长度为 n 的整数数列。
//请你使用快速排序对这个数列按照从小到大进行排序。
//并将排好序的数列按顺序输出。

#include<iostream>
using namespace std;

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

void quick_sort(int l, int r){
   
    if(l >= r) return;
    
    int x = q[ l + r >> 1], 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(l, j);
    quick_sort(j+1, r);
}

int main(){
   
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
   
        scanf("%d", &q[i]);
    }
    quick_sort(0, n-1);
    for(int i = 0; i < n; i++){
   
        printf("%d ", q[i]);
    }
    return 0;
}
2.归并排序——分治

1.找分界点,为数组中间的那个点。

2.递归排序:L->mid,mid+1->R

3.归并——合二为一。

模板:

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 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 (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

例题:

787 归并排序
//给定你一个长度为 n 的整数数列。
//请你使用归并排序对这个数列按照从小到大进行排序。
//并将排好序的数列按顺序输出。

#include<iostream>
using namespace std;

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

void merge_sort(int l, int r){
   
    if(l >= r) return;
    int mid = l + r >> 1;
    
    merge_sort(l, mid);
    merge_sort(mid + 1, r);
    
    int i = l, j = mid + 1;
    int k = 0;
    while(i <= mid && j <= r){
   
        if(q[i] <= q[j]) temp[k++] = q[i++];
        else temp[k++] = q[j++];
    }
    while(i <= mid) temp[k++] = q[i++];
    while(j <= r) temp[k++] = q[j++];
    
    for(int i = l, j = 0; i <= r; i++, j++){
   
        q[i] = temp[j];
    }
}

int main(){
   
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
   
        scanf("%d", &q[i]);
    }
    merge_sort(0, n-1);
    for(int i = 0; i < n; i++){
   
        printf("%d ", q[i]);
    }
}

二分法

1.整数二分

基础的mid写法为:mid = (l + r) / 2

但这种写法当l和r特别大的时候mid会得到一个负数,所以求mid时一般写成:

mid = l + (r - l) / 2,上取整为mid = l + (r - l + 1) / 2

模板1:

// 区间[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;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}

模板2:

// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_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;
}

例题:

789 数的范围
//给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
//对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
//如果数组中不存在该元素,则返回 -1 -1。

#include<iostream>
using namespace std;

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

int main(){
   
    cin>>n>>q;
    for(int i = 0; i < n; i++){
   
        cin>>arr[i];
    }
    while(q--){
   
        int k;
        cin>>k;
        
        int l = 0, r = n - 1;
        while(l < r){
   
            int mid = l + (r - l) / 2;
            if(arr[mid] >= k) r = mid;
            else l = mid + 1;
        }
        if(k != arr[l]) cout<<"-1 -1"<<endl;
        else{
   
            cout<<l<<" ";
            int l = 0, r = n - 1;
            while(l < r){
   
                int mid = l + (r - l + 1) / 2;
                if(arr[mid] <= k) l = mid;
                else r = mid - 1;
            }
            cout<<l<<endl;
        }
    }
    return 0;
}
2.实数(浮点数)二分

一般eps取比题目要求精度多乘0.01就行

模板:

double bsearch_3(double l, double r)
{
   
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
   
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

例题:

790 数的三次方根
//给定一个浮点数 n,求它的三次方根。

#include<bits/stdc++.h>
using namespace std;

int main(){
   
    double n;
    cin>>n;
    double l = -10000, r = 10000;
    while(r - l > 1e-8){
   
        double mid = l + (r - l) / 2;
        if(mid*mid*mid >= n) r = mid;
        else l = mid;
    }
    printf("%lf", l);
    return 0;
}

高精度

1.高精度加法

1.用vector去储存两个数,把两个数定义成String,然后拆分每一位给vector。

2.由于在vector后添加数据更为简单,所以在存储时将数的低位放到数组前面。

模板:

vector<int> add(vector<int> &A, vector<int> &B){
   
    vector<int> C;
    
    int temp = 0; //进位数
    for(int i = 0; i < A.size() || i < B.size(); i++){
    //只要A,B长度有一个没加完,就继续加
        if(i < A.size()) temp += A[i];
        if(i < B.size()) temp += B[i]; 
        C.push_back(temp % 10); //要用push_back,不能数组用数组下标赋值
        temp /= 10;
    }
    
    if(temp) C.push_back(temp); //最后一个进位肯定为1
    return C;
}

例题:

791 高精度加法
//给定两个正整数(不含前导 0),计算它们的和。

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

vector<int> add(vector<int> &A, vector<int> &B){
   
    vector<int> C;
    
    int temp = 0; //进位数
    for(int i = 0; i < A.size() || i < B.size(); i++){
    //只要A,B长度有一个没加完,就继续加
        if(i < A.size()) temp += A[i];
        if(i < B.size()) temp += B[i]; 
        C.push_back(temp % 10); //要用push_back,不能数组用数组下标赋值
        temp /= 10;
    }
    
    if(temp) C.push_back(temp); //最后一个进位肯定为1
    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');
    auto C = add(A, B);
    for(int i = C.size() - 1; i >= 0; i--) cout<<C[i];
    return 0;
}
2.高精度减法

默认给的两个整数都是正整数。

A, B, t(下一位找本位借位的数)

t = 0表示没有借位,t = 1表示借位了

t也表示当前位的值。

1.先判断A和B谁大,如果A - B >= 0,那就是A - B;如果A - B < 0,那就是B - A。

2.如果A - B - t >= 0,那结果就是A - B - t;如果A - B - t < 0,那结果就是A - B - t + 10。

模板:

// 判断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, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B)
{
   
    vector<int> C;
    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();// 去掉前导0
    return C;
}

例题:

792 高精度减法
//给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。

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

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

vector<int> sub(vector<int> &A, vector<int> &B){
   
    vector<int> C;
    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();
    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--) cout<<C[i];
    }else{
   
        auto C = sub(B, A);
        cout<<"-";
        for(int i = C.size() - 1; i >= 0; i--) cout<<C[i];
    }
    return 0;
}
3.高精度乘法

高精度*低精度

1.用A的每一位去乘以b,得出来的数%10是当前位的值,/10是进位的值,用一个t既代表了进位,又代表了当前的值,每一轮循环最后再更新t的值,t /= 10。

模板:

// 高精度乘低精度
// C = A * b, A >= 0, b > 0
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(); //处理前导0
    
    return C;
}

例题:

793 高精度乘法
//给定两个非负整数(不含前导 0) A 和 B,请你计算 A×B 的值。

#include<iostream>
#include<vector>
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);
    for(int i = C.size() - 1; i >= 0; i--) cout<<C[i];
    
    return 0;
}
4.高精度除法

高精度/低精度

1.除法与其它三种算法不同,它不需要倒序存储,因为除法是从高位开始除的,但是为了保持和那三种算法的一致,我们还是用倒序存储高精度的数。

2.在处理商C的时候,需要将其反转一下,因为这样可以更好的消去它的前导0,并且不会在主函数输出的时候写法和前三种不同。

模板:

// 高精度除以低精度
// A / b = C ... r, A >= 0, b > 0
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 = 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;
}

例题:

794 高精度除法
//给定两个非负整数(不含前导 0) A,B,请你计算 A/B 的商和余数。
    
#include<iostream>
#include<vector>
#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 = 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--) cout<<C[i];
    cout<<endl<<r;
    return 0;
}

前缀和与拆分

1.一维前缀和

模板:

// 一维前缀和
// S[i] = a[1] + a[2] + ... a[i]
// a[l] + ... + a[r] = S[r] - S[l - 1]

例题:

795 前缀和
// 输入一个长度为 n 的整数序列。
//接下来再输入 m 个询问,每个询问输入一对 l,r。
//对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。

#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], S[N];

int main(){
   
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);     // 这里都从下标1开始,因为题目给的l,r都是第几个数,而不是数组下标
    for(int i = 1; i <= n; i++) S[i] = S[i - 1] + a[i];

    while(m--){
   
        int l, r;
        scanf("%d%d", &l, &r);
        cout<<S[r] - S[l - 1]<<endl;
    }
    return 0;
}
2.二维前缀和

模板:

// 二维前缀和
// S[i, j] = 第i行j列格子左上部分所有元素的和
// 以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为 S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

例题:

796 子矩阵的和
//输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
//对于每个询问输出子矩阵中所有数的和。

#include<iostream>
using namespace std;

const int N = 1e3 + 10;
int a[N][N], S[N][N];
int n, m, q;

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++)      
            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);
        cout<<S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]<<endl;
    }        
    return 0;
}
3.一维差分

一个思想就是一开始认为a数组和b数组都为0,后面只需要插入一遍就可以了。

to me:看的时候如果看不明白就列一下递推式子看。bn = an - an-1

模板:

// 一维前缀和
// S[i] = a[1] + a[2] + ... a[i]
// a[l] + ... + a[r] = S[r] - S[l - 1]

例题:

797 差分
//输入一个长度为 n 的整数序列。
//接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
//请你输出进行完所有操作后的序列。

#include<iostream>
using namespace std;

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

void insert(int l, int r, int c){
   
    b[l] += c; // 下标为l的差分数组+c就可以改变其前缀和数组l及以后的数了
    b[r + 1] -= c; // 对应的要在区间范围外的数减去c
}

int main(){
   
    cin>>n>>m;
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++) insert(i, i, a[i]); // 这一步已经构造完差分数组了
    
    while(m--){
   
        int l, r, c;
        scanf("%d%d%d", &l, &r, &c);
        insert(l, r, c);
    }
    
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值