Acwing菜鸟闯关记(一)

Acwing菜鸟闯关记(一)

鄙人机试能力极差,相关算法与板子记不住,借助y神Acwing平台,希望能给自己的机试能力上升几个段位。目前是算法基础课的第一讲。

快速排序

785.快速排序

对于快速排序的板子,关键在于左右双指针是如何移动的,笔者认为关键在于哨兵。
选取的哨兵,第一次快排后左边的都是比它小的,右边都是比他大的。之后再不断递归,当l>=r时,跳出递归。

void quicksort(int l,int r,int a[]){
    if (l>=r)
        return;
    int rnd = rand()%(r-l+1)+l;
//    printf("%d",a[rnd]);
    swap(a[rnd],a[l]);
    int x = a[l];
    int i=l-1,j=r+1;
    while(i<j){
        do i++; while (x>a[i]);
        do j--; while (x<a[j]);
        if (i<j)
            swap(a[i],a[j]);
    }
    quicksort(l,j,a);
    quicksort(j+1,r,a);
}

需要注意的是,选取a[l]是有风险的。不如随机数更合适。l>=r是个界限,注意这种情况下不必再递归。
rnd = rand()%(r-l+1)

786.找第k个数

void quicksort(int a[],int l,int r){
    if (l>=r)
        return;
//    int rnd = rand()%(r-l+1)+l;
//    swap(a[l],a[rnd]);
    int i = l-1,j = r+1;
    int x = a[l];
    while(i<j){
        do i++; while (x>a[i]);
        do j--; while (x<a[j]);
        if (i<j)
            swap(a[i],a[j]);
    }
    if (k-1<=j)
        quicksort(a,l,j);
    else
        quicksort(a,j+1,r);
}

与快排相同,由于选中的是第k个数,所以用快排更加迅捷。注意区分分界点。

快速排序时间复杂度最差:O(n^2);
平均时间复杂度:O ( n l o g 2 n )
快速排序是不稳定的。

归并排序

787.归并排序

归并排序的难点在于开创tmp数组进行两个序列的比较。
需要有mid进行确定。

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

注意是先递归,之后合并。
l>=r不能忘记,同时最后赋值回原数组时应确定a数组的初始与tmp数组的初始下标。
归并排序是稳定的。
时间复杂度:O ( n l o g 2 n ) 。

788.逆序对的数量

LL merge_find(int l,int r){
    if (l>=r)
        return 0;
    int mid  = (l+r)>>1;
    LL res = merge_find(l,mid) + merge_find(mid+1,r);
    int i = l,j = mid+1;
    int k = 0;
    while(i<=mid&&j<=r){
        if (a[i]<=a[j]) tmp[k++] = a[i++];
        else {
            tmp[k++] = a[j++];
            res += mid-i+1;
        }
    }
    while(i<=mid)   tmp[k++] = a[i++];
    while (j<=r)    tmp[k++] = a[j++];
    for (int i = l,j=0; i <=r ; ++i,++j) {
        a[i] = tmp[j];
    }
    return res;
}

难点在于如何找出逆序对数量,按照归并排序正常进行。但到后面采用,res+=mid-i+1直接选用。因为局部有序,该数符合逆序对,后面的都是符合要求的逆序对。

二分查找

789.数的范围(整数二分)

    while(q--){
        int find;
        scanf("%d",&find);
        int l=0,r=n-1;
        while(l<r){
            int mid = l + r >> 1;
            if(a[mid]>=find) r = mid ;
            else l = mid+1;
        }
        if(a[l] != find)   cout<<"-1 -1"<<endl;
        else {
            cout<<l<<" ";
            r = n-1;
            while(l<r){
                int mid = l+r+1 >>1;
                if (a[mid] <= find)  l = mid;
                else r = mid-1;
            }
            cout<<r<<endl;
        }
    }

整数二分的问题在于,是分割成[l,mid-1]和[mid,r]还是[l,mid]和[mid+1,r]。
C语言向下取整,选择[l,mid-1]和[mid,r]则需要mid = l+r+1>>1
选择[l,mid]和[mid+1,r],则mid= l+r>>1

790.数的三次方根(实数二分)

    double l = -10000,r = 10000;
        while(r-l > 1e-8) {
            double mid = (l + r) / 2;
            if (mid * mid * mid >= q)
                r = mid;
            else l = mid;
        }

实数二分注意区间即可。

高精度

791.高精度加法

vector<int> add(vector<int> &A,vector<int> &B){
    int t=0;
    vector<int> C;
    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!=0) C.push_back(t);
    return C;
}

    vector<int> A,B;
    cin>>a;//1234
    cin>>b;
    for (int i = a.length()-1; i >= 0; i--) {
        A.push_back(a[i]-'0');
    }
    for (int i = b.length()-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];
    }

首先注意读进来后要进行逆置,因为先进行个位加。
之后进行高精度加,每一位加完之后形成的和要进行对10取余留在本位,和除以十进位。
注意输出的时候也应该逆置输出。

792.高精度减法

// 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 用来借位
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];  //判断B是否有这一位
        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;
}

其他操作类同高精度加法,但应注意到减法应交换数,保证大数减小数。

793.高精度乘法

vector<int> mul(vector<int> &A,int b){
    int t=0;
    vector<int> C;
    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;
}

794.高精度除法

vector<int> div(vector<int> &A,int b,int& r){
    r=0;
    vector<int> C;
    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.back()==0&&C.size()-1) C.pop_back();
    return C;
}

前缀和与差分

795.前缀和

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

前缀和的目标就是优化,让时间复杂度从O(n)变O(1)

796.前缀和子矩阵

    for (int i = 1; i <= n; ++i) {
       for (int j = 1; j <= m ; ++j) {
           cin>>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]+a[i][j]-S[i-1][j-1];
       }
   }
   while(q--){
       int x1,y1,x2,y2;
       scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
       cout<<S[x2][y2]+S[x1-1][y1-1]-S[x2][y1-1]-S[x1-1][y2]<<endl;
   }

前缀和子矩阵的问题是注意加回重复值。
前缀和数组注意开头下标是1,否则越界。

797.差分

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

注意insert函数,差分的本质
b1 = a1; b2 = a2 - a1 … bn = bn - bn-1
使区间[l,r]中的每一个a[i]+c,则只需b[l]+c,b[r+1]-c。
注意下标开始也是1

798.差分矩阵

与前缀和看的矩阵相反,类比差分

void insert(int x1,int x2,int y1,int y2,int c){
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}
 
 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,i,j,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,x2,y1,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];
            printf("%d ",b[i][j]);
        }
        puts("");
    }

给以(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

双指针

799.最长连续不重复子序列

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

两个指针同时从开头走,i,j注意。

位运算

801.二进制中1的个数

int lowbit(int n){
    return n & -n;
}

while(x){
    x -= lowbit(x);
    res++;
}

注意x -= lowbit(x)
取最低位操作 x & 1

离散化

802.区间和

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

typedef pair<int,int> PII;
const int N = 300010;
int a[N],s[N],n,m;

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 r = find(item.second),l= find(item.first);
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;
}

难点
用到前缀和求区间内值的和。(数太多,超出界限)
首先去重:先将数组进行排序,利用unique,erase去重。
之后二分查找。
最后构造离散后的大数组。

区间合并

803.区间和并

按照区间左端点排序。
初始化左st右ed边界为-2e9。
比较ed与当前所在区间左端点,更新区间,并将更新后的区间记录下来。
返回数组长度。
注意引用的应该加&

void merge(vector<PII> &seg){
    vector<PII> res;
    sort(seg.begin(),seg.end());
    int st = -2e9,ed = -2e9;
    for (auto item:seg) {
        if (ed < item.first){
            if (st!=-2e9)   res.push_back({st,ed});
            st = item.first,ed = item.second;
        }
        else {
            ed = max(ed,item.second);
        }
    }
    if (st!=-2e9)   res.push_back({st,ed});
    seg = res;
}

加油!拼搏百天!
HEBUT 作者:陈冰yunshangjin
邮箱:suchenbin@163.com 可以发邮件进行交流

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值