对acwing基础课第一章的总结

对基础课第一章的总结

第一章内容包括:

快速排序,归并排序,
二分(两种二分划定)
高精度
前缀差分(一维二维)
双指针算法的思路
位运算
离散化 区间的合并

快速排序
思路:
1.先任意选择数组内的一个数字x作为快排的划分点。 利用两个指针i,j ,将小于x的数字移动到x的左边,大于x的数字放在x的右边。
2.再通过分治思路,将x及左边的数组进行1.操作的步骤。x右边的数组也同样进行1.操作的步骤。

最后不用合并数组,因为一步一步往小区间的划分排序,就会使整个数组最终呈现有序的状态

时间复杂度 一般是O(nlogn) ,最坏O(n^2)
代码:

#include <iostream>
using namespace std;
const int maxn=1e6+10;
int q[maxn];
void quick_sort(int l,int r){
    if(l>=r) return;  //终止递归的条件
    int i=l-1,j=r+1,x=q[l+r>>1];  //i指向左边界的前一个位置 j指向右边界的后一个位置 便于指针移动
    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() {
	int n;
	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;
}

归并排序
思路:与快排相反,它是一个先划分区间,再排序,最后合并区间的过程
但归并排序需要一个额外的临时数组tem,用来存放小区间内排序好的数字,最后将tem的数字放在真正数组的 left与right区间

代码:

#include<iostream>
using namespace std;
const int maxn=1e6+5;
int p[maxn];
int tem[maxn];
void merey_sort(int p[],int l,int r){  //p排序数组   l当前p的左边界   r当前p的右边界
    if(l>=r) return;
    int mid=l+r>>1;  //因为+的优先级比>>高,所以不需要加括号
    
    merey_sort(p,l,mid); merey_sort(p,mid+1,r);//将数组先划分一层一层 的 一对区间
    
    int k=0,i=l,j=mid+1; //k是tem数组的一个指针,表示当前区间已经排序了的数字个数 
    //i是指向左半边区间的第一个数的位置  j指向右半边区间的第一个数的位置
    
    while(i<=mid && j<=r)
    if(p[i]<p[j]) tem[k++]=p[i++];
    else tem[k++]=p[j++];
    while(i<=mid) tem[k++]=p[i++];
    while(j<=r) tem[k++]=p[j++];
    for(int i=l,j=0;i<=r;i++,j++) p[i]=tem[j];  //i指向p数组的左边界,将tem数组排序后的值放在p的[l,r]区间中
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&p[i]);
    merey_sort(p,0,n-1); //初始边界为l:0  r:n-1
    for(int i=0;i<n;i++)printf("%d ",p[i]);
    return 0;
}

二分

有单调性的序列 肯定可以二分 。但二分的本质不在于单调性 ,而是边界。 意思是如果我们能找到这样一个性质,每次都可以使得该性质在这一半区间中成立,而在另一半中不成立,那么我们就可以二分,将这个边界点二分出来。

二分分为两种二分方法:
第一中是寻找大于等于 x 的首位置
第二种是寻找大于 x 的 的首位置
具体模板:
我们要注意的是,如果在二分中,l=mid ,那么在取中间值mid的时候,就需要向上整除 即 mid = l + r + 1 >> 1

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[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;
}
// 区间[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;
}

也可以用c++库函数的
lower_bound 与upper_bound

int* p = lower_bound(a, a + a.size(), x)-a; // 查找数组在指定范围内大于等于x的元素下标(要求数组有序)
int* p = upper_bound(a, a + a.size(), x)-a; // 查找数组在指定范围内大于x的元素下标(要求数组有序)

对于浮点数的二分:
这个就很简单了,不用考虑边界,直接 l =mid 以及 r = mid 就行,
但要注意的就是不同于整型的 while(l<r) 循环规则变成lwhile(r-l>exp) //exp为精度 比如.1e-6

高精度
这个就是多手写模板才能记忆了,思路就是我们日常手算思路的模拟

下面给出模板

高精度加法

// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);

    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++ )
    {
        t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }

    if (t) C.push_back(t);
    return C;
}

高精度减法

// 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();
    return C;
}


高精度乘法

// 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();

    return C;
}


高精度除法

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

前缀与差分

一维前缀:
s[i] = s[i-1] + a[i]
每个s[i]中存放的都是数组a从 0 到 i 的和
举个例子,如果我们要求a数组[l,r]区间内的和
我们用前缀和的思想,求[l,r]的和,就可以表示为 s[r] - s[l-1] 时间复杂度就是O(1)

二维前缀: 二维前缀对应的s[i][j]储存的是i,j及其左上角的矩阵和
公式:s[i] [j] = s[i-1] [j] + s[i] [j-1] - s[i-1] [j-1] + a[i] [j];

一维差分:
假设a[] b[] a储存原数组 b是a的差分数组
那么a数组是b数组的前缀和 ,b数组是a数组的差分数组
比如
首先对b进行初始化,即在每个(i,i) 的位置都在b[i]中插入a[i]
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c

二维差分:
对 b[i][j] 加减,是对矩阵右下角进行的加减 ,可拓展为任意一子矩阵的操作
例子:
S为差分矩阵
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

双指针算法思路:
可以先想一个朴素的思路,然后利用双指针将O(n^2) 转化为O(n)

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

位运算

求n的第k位数字: n >> k & 1
//这里的第k位数字是指从右往左数第012…位数字

返回n的最后一位1:lowbit(n) = n & -n
举个例子: 10的二进制是:1010 ,则 lowbit(10) = 2 ,2对应的是二进制的10,
再比如 101000 ,对lowbit操作后, 结果为1000,也就是十进制的8

离散化:

如果我们用平时的数组哈希方法来表示该数字是否存在,如果有一个数是1e18,
那我们肯定不能开a[1e18]的数组。所以就可以用离散化的方法,将每个数字表示在对应123456…为下标的区间内,便于访问

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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; // 映射到1, 2, ...n
}


区间合并:
习题:acwing 803区间合并

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int n;
typedef pair<int,int> p;
vector<p> s;
void merge(){
    vector<p>  res;
    int st=-2e9,en=-2e9;
    for(int i=0;i<s.size();i++){
        if(en<s[i].first){
            if(st!=-2e9) res.push_back({st,en});
            st=s[i].first;en=s[i].second;
        }
        else en=max(en,s[i].second);
    }
    if(st!=-2e9) res.push_back({st,en});
    s=res;
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r; cin>>l>>r;
        s.push_back({l,r});
    }
    
    sort(s.begin(),s.end());
    merge();
    cout<<s.size();
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值