算法竞赛备赛之经典基础算法训练提升,暑期集训营培训

 

目录

1.排序

1.1.快速排序

1.2.归并排序

2.二分

2.1.整数

2.2.浮点数

3.高精度

3.1.高精度加法

3.2.高精度减法

3.3.高精度乘法

3.4.高精度除法

4.前缀和

5.差分

6.双指针算法

7.位运算

8.离散化

8.1.unique函数实现

9.区间合并


1.排序

1.1.快速排序

快速排序的基本思想来自于分治。

首先,确定分界点的方法:

  1. q[left];

  2. q[(left + right) / 2];

  3. q[right];

  4. 随机

第二步,则是调整区间,按照相应的要求,将数值划分为两部分。

第三步,递归处理左右两段数值。

使用两个指针分别指向左右两端,判断左右两端与指定的分界值x之间的关系,做出相应的调整。

快速排序模板

#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 x = q[l], 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);
}
​
int main()
{
    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;
}

1.2.归并排序

也是通过分治的思想,将数组分成left和right

  1. 确定

  2. 递归排序left、right

  3. 归并——合二为一

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== 编辑
#include<iostream>
​
using namespace std;
​
#define N 10000
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 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];
    }
}
​
int main()
{
    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;
}

vector容器的模板

void merge_sort(vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}
​
void merge(vector<int>& arr, int left, int mid, int right) {
    vector<int> left_arr(arr.begin() + left, arr.begin() + mid + 1);
    vector<int> right_arr(arr.begin() + mid + 1, arr.begin() + right + 1);
    int i = left - 1;
    int j = mid;
    int k = 0;
    while (k < right - left + 1) {
        if (j == right) {
            arr[++i] = left_arr[++k];
        } else if (left_arr[++k] <= right_arr[j]) {
            arr[++i] = left_arr[k];
        } else {
            arr[++i] = right_arr[j];
        }
    }
}

其中,merge_sort函数是递归函数,将待排序的数组分为左右两部分,再分别调用merge_sort函数进行排序。merge函数则是将左右两个已排序的数组合并成一个有序数组。

2.二分

通过某一条性质,每一次从中间进行分割,一半满足条件,一般不满足。不满足的那一部分就直接毙掉,满足的那一部分在调用二分法进行框定界限,从而使确定值越来越接近。

2.1.整数

前提是要排好序的,且必须要有解。

整数二分法题目 - 数的范围

#include<iostream>
using namespace std;
​
const int N = 1e5 + 10;
​
int n, m;
int q[N];
​
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0;i < n; i++)
    {
        scanf("%d", &q[i]);
    }
    
    while(m--)
    {
        int x;
        scanf("%d", &x);
        
        int l = 0,r = n - 1;
        while(l < r)
        {
            int mid = l + r >> 2;
            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;
                if(q[mid] <= x) l = mid;
                else r = mid - 1;
            }
            
            cout << l << endl;
        }
    }
    
    return 0;
}

2.2.浮点数

开平方

#include<iostream>
using namespace std;
​
int main()
{
    double x;
    scanf("%lf", &x);
    
    double l = 0, r = x;
    while(r - l > 1e-8)//经验值,比要求小数多两位
    {
        double mid = (l + r) / 2;
        if(mid * mid >= x) r = mid;
        else l = mid;
    }
    printf("%lf", l);
    return 0;
}

3.高精度

3.1.高精度加法

两个正整数相加

#include<iostream>
#include<vector>
using namespace std;
​
const int N = 1e6 + 10;
​
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.push_back(a[i] - '0');//A = [6,5,4,3,2,1]
    }
    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;
}

3.2.高精度减法

两个正整数相减。

首先要考虑到正负号,当小数减大数的时候,一定是负的;

其次是一位不够,向上借位。

#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;
    int t = 0;
    for(int i = 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.back();//去掉前导0
    return C;
}
​
int main()
{
    string a, b;
    vector<int> A, B;
    
    cin >> 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("-");
        for(int i = c.size() - 1;i >= 0; i--)
        {
            printf("%d",c[i]);
        }
    }
    return 0;
}

3.3.高精度乘法

两个正整数相乘,长整数乘上短整数的代码。

#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;
    }
    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--)
    {
        printf("%d",C[i]);
    }
    
    return 0;
}

3.4.高精度除法

高精度的整数除以低精度的整数。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
​
// A/b,商是C,余数是r
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--)
    {
        printf("%d",C[i]);
    }
    cout << endl << r << endl;
    
    return 0;
}

4.前缀和

Si = a1 + a2 + a3 + ..... + an (注意:S0 = 0)

如何求Si:

for(int i = 1;i <= n; i++)
{
    S[i] = S[i-1] + a[i];
}

作用:[l, r] 时间复杂度O(n)

用一次运算来计算任意段内的值:Sr - Sl-1

#include<iostream>
using namespace std;
​
const int N = 100010;
​
int n, m;
int a[N], s[N];
​
int main()
{
    ios::sync_with_stdio(false);//对cin的判断
    
    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;
}

图的面积裁剪问题

求和S[i] [j],这时候能用我们熟知的割补法来进行计算

S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1- 1][y1 -1];

这一块还是简单的,代码也是好写的

#include<iostream>
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);
    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);
        printf("%d", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1 -1]);//算部分矩阵和
    }
    return 0;
}

5.差分

是前缀和的逆运算

a1, a2, .... an 构造b1, b2, ... , bn

b1 =a1 b2 = a2 - a1 bn = an - an-1

a就是b的前缀和,b就是a的差分

差分题目

输入一段长度为n的整数序列

接下来输入m个操作,每个操作包含三个整数l,r,c,表示将序列中[l,r]之间的每个数加上c,输出进行完所有操作后的序列。

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

我们上面的代码是一维的差分,接下来我们来构造一个差分矩阵。

b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += 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, x2, y1, 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++)
        {
            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]);
        }
    }
    
    return 0;
}

6.双指针算法

for(int i = 0, j = 0;i < n; i++)
{
    while(j < i && check(i, j)) j++;
    
    //每道题目的具体思路
}

核心思想

for(int i = 0;i < n; i++)
{
    for(in j = 0;j < n; j++)
}
//算法复杂度O(n^2)

双指针算法能将上述的朴素算法优化到O(n)

输出字符串的每个单词

前提每个单词间只有一个空格。

#include<iostream>
#include<string.h>
using namespace std;
​
int main()
{
    char str[1000];
    
    gets(str);
    
    int n = strlen(str);
    
    for(int i = 0;i < n; i++)
    {
        int j = i;
        while(j < n && str[j] != ' ')
        {
            j++;
        }
        
        //本题的具体逻辑
        for(int k = i;k < j; k++)
            cout << str[k];
        cout << endl;
        
        i = j;
    }
    return 0;
}

最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续序列,输出它的长度。

//朴素算法

for(int i = 0;i < n; i++)
{
    for(int j = 0;j <= i; j++)
    {
        if(check(j, i))
        {
            res = max(res, i - j + 1);
        }
    }
}

//双指针算法

for(int i = 0, j = 0;i < n; i++)
{
    while(j <= i && check(j, i)) j++;
    res = max(res, i - j + 1);
}

j:j往左最远能到的位置

题目代码模板

#include<iostream>
​
using namespace std;
​
const int N = 100010;
​
int n;
int a[N], s[N];
​
int main()
{
    scanf("%d", &n);
    for(int i = 0;i < n; i++)
    {
        scanf("%d", &a[i]);
    }
    
    int res = 0;
    for(int i = 0, j = 0;i < n; i++)
    {
        s[a[i]]++;
        while(s[a[i]] > 1)
        {
            s[a[j]]--;
            j++;
        }
        
        res = max(res, i - j + 1);
    }
    
    printf("%d\n", res);
    
    return 0;
}

7.位运算

n的二进制表示第k位

  1. 先把第k位数字移到最后一位 n >> k

  2. 再观察个位的数字 x & 1

  3. lowbit(x):返回x的最后一位 x & -x = x & (~x + 1)

#include<iostream>
using namespace std;
​
int lowbit(int x)
{
    return x & -x;
}
​
int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int x;
        cin >> x;
        
        int res = 0;
        while(x) x -= lowbit(x), res++;//每次减去x的最后一位
        
        cout << res << ' ';
    }
    
    return 0;
}

数据在内存中以2进制的形式存储 对于整数来说: 整数二进制有3种表示形式:原码、反码、补码 正整数:原码、反码、补码相同 负整数:原码、反码、补码要进行计算的 按照数据的数值直接写出的二进制序列就是原码 原码的符号位不变,其他位按位取反,得到的就是反码 反码+1,得到的就是补码

存储举例1:

int a = -10;

1000 0000 0000 0000 0000 0000 0000 1010 - 原码

1111 1111 1111 1111 1111 1111 1111 0101 - 反码

1111 1111 1111 1111 1111 1111 1111 1110 - 补码

FFFFFFF6-内存存储

内存存补码。

#include<iostream>
using namespace std;
​
int lowbit(int x)
{
    return x & -x;
}
​
int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int x;
        cin >> x;
        
        int res = 0;
        while(x) x -= lowbit(x), res++;
    
        cout << res << ' ';
    }
    return 0;
}

8.离散化

假设有一串极大的数据,我们对这串数据a[i]进行离散化,其中a[i]中可能会有重复的元素,首先就是处理去重的问题,然后再进行一一映射。

vector<int>alls;//存储所有有待离散化的值
sort(alls.begin(), alls.end());//将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());//去重
​
//二分法求出x对应的离散化的值
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 r + 1;
}

例题:区间和

假设有一个无限长的数轴,数轴上的每个坐标上的数都是0。

现在,我们首先进行n次操作,每次操作将某一位置x上的数+c

接下来,进行m次询问,每次询问包含两个整数l和r,你需要求出区间l和r之间所有数的和。

#include<iostream>
#include<vector>
#include<algorithm>
​
using namespace std;
​
typedef pair<int, int> PII; 
​
const int N = 300010;
​
int n, m;
int a[N], s[N];
​
vector<int> alls;
vector<PII> add,query;
​
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 r + 1;
}
​
int main()
{
    cin >> n >> m;
    for(int i = 0;i < n; i++)
    {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        
        alls.push_back(x);
    }
    
    for(int i = 0;i < m; i++)
    {
        int l, r;
        cin >> l >> r;
        query.push_back({l, r});
        
        alls.push_back(l);
        alls.push_back(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), r = find(item.second);
        cout << s[r] - s[l-1] << endl;
    }
    
    return 0;
}

8.1.unique函数实现

vector<int>::iterator unique(vector<int> &a)
{
    int  j = 0;
    for(int i = 0;i < a.size(); i++)
    {
        if(!a || a[i] != a[i - 1])
            a[j++] = a[i];
    }
    //a[0]~a[j-1]所有的数都是不同的
    return a.begin() + j;
}

排完序的内容中,数要么就是第一个,要么就是a[i] ≠a[i - 1],Java和python方向是没有unique函数的,如果要写的话,就是做一个这样的迭代器。

9.区间合并

假设有大量区间,有的区间之间有重叠的部分,我们要将有交集的区间进行合并。

  1. 按区间左端点排序

  2. 扫描整个区间,将所有可能有交点的区间进行合并

#include<iostream>
#include<vector>
#include<algorithm>
​
using namespace std;
​
typedef pair<int, int> PII;
​
const int N = 100010;
​
int n;
vector<PII> segs;
​
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()
{
    cin >> n;
    
    for(int i = 0;i < n; i++)
    {
        int l ,r;
        cin >> l >> r;
        segs.push_back({l,r});
    }
    
    merge(segs);
    
    cout << segs.size() << endl;
    
    return 0;
}

 

 

  • 43
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 45
    评论
评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Williamtym

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值