一、基础算法之排序、二分、高精度、前缀和与差分、双指针算法、位运算、离散化、区间合并内容。

1.快速排序

算法思想:选择基准元素,比基准元素小的放左边,比基准元素大的放右边。每趟至少一个元素排好。

每一趟实现步骤:

  • low>=high,返回,排序完成
  • 选取基准元素x=a[low],i=low,j=high
  • 当i<j时,转4
  • 从右至左找到第一个小于x的元素,移到a[i]的位置
  • 从左至右找到第一个大于x的元素,移到a[j]的位置,转3

参考:最详细的排序算法——快速排序 - 知乎 

  • 循环结束,i=j,将a[i]=x,此时左边的元素小于x,右边的元素大于x,x排好
  • 对x左边进行递归排序
  • 对x右边进行递归排序

代码实现:

#include <iostream>
using namespace std;

const int N = 100010;

int n;
int q[N];

void quick_sort(int a[], int low, int high){

    if (low >= high) return;
+
    int x = a[low],i=low,j=high;
    while(i < j){                       // 边界条件
        while(a[j]>x && i<j) j--;       //右侧的数均大于x,条件i<j防止越界
        if(i<j){
            a[i] = a[j];
            i++;
        }
        while(a[i]<x && i<j) i++;      // 左侧的数均大于x,条件i<j防止越界
        if(i<j){
            a[j] = a[i];
            j--;
        }
    }
    a[i] = x;                         // i=j为x的位置,此时左侧的数小于x,右侧的数大于x,一趟元素排好
    quick_sort(a,low,i-1);
    quick_sort(a,i+1,high);

}

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

应用

给定一个长度为 n的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k个数。

分析:题目只要求找到第k个数,直接排序会用力过猛,时间复杂度更高。

算法思想:采用快排的思想,取基准元素x,进行一趟快速排序

  • 若x的位置i小于k,则应该在右侧寻找

  • 若x的位置i大于k,则应该在左侧寻找 

 

  • 若i的位置等于k,则找到该元素,返回 

代码实现:

#include <iostream>
using namespace std;

const int N = 100010;

int n;
int q[N];

int quick_sortk(int a[], int low, int high, int k){

    int x = a[low],i=low,j=high;
    while(i < j){                   
        while(a[j]>x && i<j) j--;       //右侧的数均大于x,条件i<j防止越界
        if(i<j){
            a[i] = a[j];
            i++;
        }
        while(a[i]<x && i<j) i++;      // 左侧的数均大于x,条件i<j防止越界
        if(i<j){
            a[j] = a[i];
            j--;
        }
    }
    a[i] = x;                           // i=j,此时为x的位置
    if(i == k) return a[i];            // i的位置等于k,返回
    else if(i < k) quick_sortk(a,i+1,high,k); // i小于k,在右侧寻找
    else quick_sortk(a,low,i-1,k);     // i大于k,在左侧寻找
}

int main()
{
    int n,k, r;
    cin >> n >>k;
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    r = quick_sortk(q,0,n-1,k-1);
    printf("%d ", r);
    return 0;
}

2.归并排序

算法分析:我们知道直接插入排序在元素有序时,时间复杂度可以达到O(N),归并排序则是借助于顺序,从1个元素开始归并,让每一段接近于有序,然后实行有序表的归并。

实现步骤:

  • 递归终止条件,l>=r,则返回
  • 从中间分开,递归归并左侧于右侧,递归执行时,会压入栈中,直到归并段为1
  • 进行有序表的归并,利用双指针不回溯,谁小谁尾差进辅助数组中,然后后移。

代码实现:

#include <iostream>

const int N = 100010;
int q[N];
void merge_sort(int a[],int l, int r){

    if(l>=r) return;

    int mid = (l+r)/2;       //从中间分开
    merge_sort(a,l,mid);     // 递归归并左侧
    merge_sort(a,mid+1,r);   // 递归归并右侧

    int length = r-l+1;      // 辅助数组长度
    int temp[length];        // 开辟辅助数组
    int i=l,j=mid+1,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(i=0;i< length;i++) q[l+i] = temp[i];  // 将有序辅助数组的内容存放回原数组中

}

int main()
{
    int n;
    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;
}
应用: 逆序对的数量

给定一个长度为 n的整数数列,计算数列中的逆序对的数量。

算法思想:

1.采用分治的思想解决问题,对于每一次分段,逆序对有三种类型,即左侧逆序对的数量、右侧逆序对的数量、左右两侧逆序对的数量。左侧逆序对的数量和右侧逆序对的数量均可以通过分治转化为求左右两侧逆序对的数量

2. 如何求解左右两侧逆序对的数量呢?在进行归并合并时。如果a[i]>a[j],那么a[j]左侧同一段内的所有元素均能与a[i]组成一对逆序对。

代码实现:

#include <iostream>
using namespace std;

const int N = 100010;
int q[N];

void reverse_order(int a[],int l, int r, int* number){

    if(l>=r) return;

    int mid = (l+r)/2;       //从中间分开
    reverse_order(a,l,mid,number);     // 递归归并左侧
    reverse_order(a,mid+1,r,number);   // 递归归并右侧

    int length = r-l+1;      // 辅助数组长度
    int temp[length];        // 开辟辅助数组
    int i=l,j=mid+1,k=0;
    while(i<=mid && j<=r){   // 双指针不回溯合并有序段
        if (a[i]<= a[j])
            temp[k++] = a[i++];
        else{
            temp[k++] = a[j++];*number+=mid-i+1; // 如果a[i]>a[j],那么a[j]左侧同一段内的所有元素均能与a[i]组成一对逆序对。
        }
    }

    while(i<=mid) temp[k++] = a[i++];  // 将剩余部分插入有序链表中
    while(j<=r) temp[k++] = a[j++];

    for(i=0;i< length;i++) a[l+i] = temp[i];  // 将有序辅助数组的内容存放回原数组中

}

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

3.二分

(1)整数二分

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

算法分析:此处的二分是二分查找的拓展,要求能够找到与查询key相等的所有元素的起始位置和终止位置。

算法思想:

1.寻找左边界: 

  • mid=(low+high),即向下取整。
  • 当a[mid]<key时,low=mid+1,最终,low会位于相等元素的起始位置。
  • 当a[mid]>=key时,让high=mid,由于向下取整,此时high会逐渐的靠近low,可将low=high作为循环终止条件。

2.寻找右边界

  • mid=(low+high+1)/2,即向上取整
  • 当a[mid]>key,时high=mid-1;最终,high指向相等元素的终止位置。
  • 当a[mid]<=key时,low=mid.此时由于向上取整,low会逐渐靠近high,可将low=high作为循环终止条件。

代码实现: 

#include <iostream>
using namespace std;

const int N = 100010;
int q[N];

int bin_search_l(int a[], int low, int high, int k){ // 找左边界

    if(low>=high) return low;  // low=high时,返回左边界
    int mid = (low+high)/2;
    if(a[mid]<k) return bin_search_l(a,mid+1, high,k); // 寻找左边界
    else return bin_search_l(a,low,mid,k);             // 由于寻找的是左边界,要求右边界趋近于左边界,即r=mid,条件包含a[mid]=k
}

int bin_search_r(int a[], int low, int high, int k){ // 找右边界

    if(low>=high) return low;  // low=high时,返回右边界
    int mid = (low+high+1)/2;   // +1是为了防止死循环,这里要求左边界趋近于右边界,需要向上取整
    if(a[mid]<= k) return bin_search_r(a,mid, high,k); // // 由于寻找的是右边界,要求左边界趋近于右边界,即l=mid,条件包含a[mid]=k
    else return bin_search_r(a,low,mid-1,k);// 寻找右边界
}


int main()
{
    int n;         // n为数组长度
    int m;         // m为查询元素个数
    int l,r;
    int key;
    cin >> n >> m;
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    while(m--){
       scanf("%d",&key);
       l = bin_search_l(q,0,n-1,key);
       if(q[l]!=key) printf("%d %d",-1, -1);
       else{
        printf("%d ",l);
        r = bin_search_r(q,0,n-1,key);
        printf("%d\n",r);
       }

    }
    return 0;
}

(2)浮点数二分

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

浮点数二分的循环终止条件为high-low<1e-8,此时不用太过考虑终止条件。

#include <iostream>
#include <cmath>

using namespace std;

int main(){

    double m;
    cin >> m;
     double l,r;
    if(m<0){       
        l=m;r=0;  // m <-1,立方根在区间[m,0]
        if(m>-1) l = -1; //-1<m<0,立方根在区间[-1,0]
    }
    else{
        l=0;r=m;  // m>1,立方根在区间[0,m]
        if(m<1) r = 1;  // 0<m<1,立方根在[0,1]
    }
    double mid;
    while(r-l>1e-8){
        mid = (l+r)/2;
        if(pow(mid,3)<m) l=mid;
        else r=mid;
    }
    printf("%lf",l);
    return 0;

 4.高精度

高精度大整数之间的运算,此时由于数字较大,可能超出了计算机的表示范围,也可能出现溢出,使用数组存储每一个大整数。

(1)高精度加法

与我们的加法计算相似,每一位的计算结果t为对应位的数字、低位进位之和。t%10为该位数字,t/10为进位。

 

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

vector<int> h_add(vector<int> &A,vector<int> &B){
    vector<int> C;
    int t=0;
    int n;
    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];
        n = t % 10;               // 当前位的数字与进位
        C.push_back(n);
        t = t / 10;
    }
    if(t) C.push_back(t);

    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');
    auto C = h_add(A,B);
    for(int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
    return 0;

}

(2) 高精度减法

与我们的减法运算原理相同,每一位的减法结果为对应位数字减法之差,同时考虑低位借位。

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


bool comp(vector<int> &A,vector<int> &B){  // 比较A,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> h_sub(vector<int> &A,vector<int> &B){
    vector<int> C;
    int t=0;
    int n;
    for(int i=0;i<A.size();i++){
        t+=A[i];
        if(i<B.size()) t-=B[i];  // 对应位数值之差
        n = (t+10) % 10;         // 每位运算结果
        C.push_back(n);
        if(t<0) t = -1;          // 若对应位数值之差小于0,则需借位
        else t = 0;
    }
    while(C.size()>1 && C.back()==0) C.pop_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(comp(A,B)){     // A>B,返回A-B
        auto C = h_sub(A,B);
        for(int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
    }
    else{             // A<B,返回-(B-A)
        auto C = h_sub(B,A);
        printf("-");
        for(int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
    }

    return 0;

}

(3)高精度乘法

根据计算机组成原理我们知道,能够通过加法和移位实现乘法运算。 即:用一个数的每一位乘以另一个数的每一位,然后移位相加。

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

vector<int> h_mul(vector<int> &A,vector<int> &B){ //使用加法和移位实现高精度乘法
    vector<int> C(A.size()+B.size(),0); // 两个n位数相乘,最多2n位
    int n,t;
    for(int i =0;i<A.size();i++){
        t = 0;
        for(int j=0;j<B.size();j++){
            t+=A[i]*B[j]+C[j+i]; //第一步每位数相乘,第二步加上移位后对应位的数
            n = t % 10;
            C[j+i] = n;
            t = t / 10;         // 低位进位
            }
        if(t) C[i+B.size()]=t; // 最后的进位

    }
    while(C.size()>1 && C.back()==0) C.pop_back();
    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');
    auto C = h_mul(A,B);
    for(int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
    return 0;

}

(4)高精度除法

类似于我们计算除法的方式,从最高位开始,依次下移移位数字,求商和余数。

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

vector<int> h_div(vector<int> &A,int B, int &r){   // 一个高精度大整数除以一个小整数
    vector<int> C;
    r = 0;
    int n;
    for(int i=A.size()-1;i>=0;i--){
        r = r *10 + A[i];       // 将下一位数字移下来,计算
        n = r / B;              // 该位的商
        C.push_back(n);
        r = r % B;             // 余数
    }
    reverse(C.begin(), C.end());   // 在数组C中,商的高位在左侧,低位在右侧,逆置数组
    while(C.size()>1 && C.back()==0) C.pop_back(); // 去除高位多余的0
    return C;
}

int main(){
    string a;
    int b;
    vector<int> A;
    cin >> a >> b;
    for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
    int r;
    auto C = h_div(A,b,r);
    for(int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
    printf("\n%d",r);
    return 0;

}

5.差分与前缀和

差分与前缀和是一对逆运算,都是以空间换时间的思想。

前缀和矩阵即存储另一个矩阵的前N项和,即

       a1  a2  a3  a4   a5   a6  a7……
s0    s1  s2  s3   s4   s5    s6  s7……
其中,s0=0,s1=a1,s2=a1+a2,s3=a1+a2+a3……,s[n]为a[n]的前缀和
          a1=s1-s0 ,a2=s2-s1, a3=s3-s2 …… ,a[n]为s[n]的差分矩阵

(1)前缀和

前缀和即通过构建前缀和数组,从而实现在O(1)的时间复杂度内求出[L,R]区间的和,其结果为S[R]-S[L-1]

实例:输入一个长度为 n 的整数序列。接下来再输入 m个询问,每个询问输入一对 l,r。

对于每个询问,输出原序列中从第 l个数到第 r 个数的和。

#include <iostream>
using namespace std;

const int N = 10010;

int main(){
    int m,n;       // m个询问,数组长度为n
    int a[N],s[N]; // a[n]为原数组,s[n]为前缀和
    s[0] = 0;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<=n;i++) s[i+1]=s[i]+a[i];  // 构建前缀和矩阵s[i]=s[i-1]+a[i],由于a[i]从0开始,
    int l,r;                                  // s[i]需要另s[0]=0,需注意下标
    while(m--){
        cin >> l >> r;   // 子区间的和
        printf("%d\n",s[r]-s[l-1]);
    }
    return 0;
}

(2)二维矩阵前缀和

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

此时,前缀和矩阵存储的结果是a[i][j]与a[1][1]子矩阵的和。

 对于前缀和矩阵的构建,s[i][j]=s[i-1][j]+s[i][j-1]+a[i][j]-s[i-1][j-1]

对于任意子矩阵的和=s[x2][y2]-s[x1-1][y1]-s[x1][y1-1]+s[x1-1][y1-1]。需要注意的是,下图中只有网格点处才表示值

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

对于每个询问输出子矩阵中所有数的和。

#include <iostream>
using namespace std;

const int N = 1010;
int a[N][N],s[N][N];

int main(){
    int m,n,q;
    cin >> n >> m >> q;
    //scanf("%d%d%d",&n,&m,&q);
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++)
            scanf("%d",&a[i][j]);
        for(int k=0;k<m;k++)
            s[i+1][k+1] = s[i+1][k]+s[i][k+1]+a[i][k]-s[i][k];
    }
    int x1,y1,x2,y2;
    while(q--){
        cin >> x1 >> y1 >> x2 >> y2;
        printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
    }
    return 0;
}

(3)差分

差分的作用是将[L,R]区间同时加c与同时减c的时间复杂度降为0(1)。即相当于让差分矩阵b[l]+c;b[r+1]-c;也就是所,在O(1)的时间复杂度内保存一次更新,所有更新累计起来,最后再用 O(N)的时间复杂度整体更新。

一维差分

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

#include <iostream>
using namespace std;

const int N = 100010;
int a[N],b[N];

void insertm(int l, int r,int c, int b[]){

   b[l] += c;
   b[r+1] -= c;

}

int main(){
    int m,n;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) insertm(i,i,a[i],b);// 初始化时,矩阵相当于对[L,L]区间加上a[i]
    int l,r,c;
    while(m--){   // m次更新
        cin >> l >> r >> c;
        insertm(l,r,c,b);
    }
    for(int i=1;i<=n;i++) {
        a[i]=a[i-1]+b[i];
        printf("%d ",a[i]);

    }
}

(4)二维差分 

二维矩阵在x1,y1,x2,y2区间内同时加c相当于对于差分矩阵b[x1][y1] += c; b[x2+1][y1] -= c; b[x1][y2+1] -= c;明显发现重叠区域多减了c,加上b[x2+1][y2+1] += c;

 

图参考:找不到页面 - AcWing 

输入一个 n行 m列的整数矩阵,再输入 q个操作,每个操作包含五个整数 x1,y1,x2,y2,c其中 (x1,y1) 和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c。

请你将进行完所有操作后的矩阵输出。

#include <iostream>
using namespace std;

const int N = 1010;
int a[N][N],b[N][N];

void insertM(int x1, int y1, int x2, int y2, int c,int b[][N]){
    b[x1][y1] += c;
    b[x2+1][y1] -= c;
    b[x1][y2+1] -= c;
    b[x2+1][y2+1] += c;
}

int main(){
    int m,n,q;//  n行m列的整数矩阵,再输入q个操作
    cin >> n >> m >> q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            insertM(i,j,i,j,a[i][j],b); // 初始化,相当于在ijij区间加上a[i][j]
        }
    }
    int x1,y1,x2,y2,c;
    while(q--){   // 指定区间+c
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insertM(x1,y1,x2,y2,c,b);
    }

    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]; // 前缀和公式
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }

}

6. 双指针

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

算法思想:采用双指针策略i,j;i,j指向不重复子序列的开始和结束,辅助数据b[n]记录i,j区间内元素出现的次数。i前移,如果出现了重复元素,则j前移,直到区间内消除重复元素。 

#include <iostream>
using namespace std;

const int N = 100010;
int a[N],b[N];

int main(){
    int n;     // 长度为n的数组
    cin >> n;
    int j = 0,res=0;  // res记录要求序列的长度
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    
    for(int i=0;i<n;i++){ // i在前,j在后,i,j之间的长度即为当前不重复序列的长度
        b[a[i]]+=1;
        while(b[a[i]]>1){  // 此时,说明a[i-1]与a[i]重复,让j前移直到序列中无重复元素
            b[a[j]]-=1;
            j++;
        }
        res = max(res,i-j+1); // 更新序列长度
    }
    printf("%d",res);
    return 0;
}

给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。数组下标从 0 开始。请你求出满足 A[i]+B[j]=x 的数对 (i,j)。数据保证有唯一解。

算法思想:寻找A[i]+B[j]=x的数对,即找一个较小的数与一个较大的数的和,使其等于x。

因此,可采用双指针,i从0开始,j从n开始,按如下步骤进行:

首先固定a[i],让j前移,如果出现了a[i]+b[j]<x,则不可能找到与a[i]搭配的数,i后移

固定a[j],让i后移,如果出现了a[i]+b[j]>x,则不可能找到与a[j]搭配的数,j前移

即:

#include <iostream>
using namespace std;

const int N = 100010;

int a[N],b[N];

int main(){
    int n,m,x;
    cin >> n >> m >> x;
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<m;i++) scanf("%d",&b[i]);
    
    int i=0,j=m-1;
    while(i<n || j>=0){
        if(a[i]+b[j]==x) {  // 找到数对
            printf("%d %d",i,j);
            break;}
        else if(a[i]+b[j] < x) i++; // 如果出现了a[i]+b[j]<x,则不可能找到与a[i]搭配的数,i后移
        else j--; // 如果出现了a[i]+b[j]>x,则不可能找到与a[j]搭配的数,j前移
    }
    return 0;
}

给定一个长度为 n的整数序列 a1,a2,…,an以及一个长度为 m 的整数序列 b1,b2,…,bm。

请你判断 a序列是否为 b序列的子序列。

子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5}是列 {a1,a2,a3,a4,a5}的一个子序列。

算法思想:该题没有要求连续的公共子序列,因此,只需要从左向右遍历a数组,判断a数组的每一个元素是否在b数组中,且保持其相对顺序。

因此:

#include <iostream>
using namespace std;

const int N = 100010;

int a[N],b[N];

int main(){
    int n,m;
    cin >> n >> m;
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<m;i++) scanf("%d",&b[i]);

    int i=0,j=0;
    while(i<n && j<m){
        if(a[i]== b[j]) i++; // a[i]在数组b中,查找a[i+1]是否在数组b中
        j++;  // 每一轮b数组始终向后走
    }
    if(i==n) printf("Yes");
    else printf("No");
}

7.位运算

让我们回到计算机组成原理的求补电路,已知[x]补,[-x]补为将[x]补最低位的0和第一位1保留,其余各位包括符号位取反。即:

[x]补=01111010

[-x]补=10000110

此时,若将x与-x,则结果为00000010,即得到x最低位的1。

应用: 

给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 11 的个数。

算法思想:每次让x减去最低位的1,然后统计 

#include <iostream>
using namespace std;

const int N = 100010;

int lowbit(int x){
    return x & -x;  // 取x中最低位的1
}

int main(){
    int n,x,res;
    cin >> n;
    while(n--){
        scanf("%d",&x);
        res = 0;
        while(x) {
            x-=lowbit(x); // x每次减去最低位的1,然后统计1的个数
            res++;
        }
        cout << res <<" ";
    }
    cout << endl;
    return 0;
}

8.区间和

示例:假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。接下来,进行 m次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r]之间的所有数的和。

 算法思想:快速求区间和,很明显可以构建前缀和数组快速求解,但是在这个场景中,数组稀疏且无限长。因此,我们可以将区间离散化,只存储需要进行处理的位置,从而降低空间复杂度。具体包括以下步骤:

  • 首先,存储位置x和区间l,r于同一数组中
  • 进行去重并且排序
  • 将位置从小到大映射为1,2,3,……
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef pair<int,int> PII;

const int N = 300010; // n+2*m,代表操作完成后非0元素的个数

int a[N],s[N];

int find(int x, vector<int> &all){
    int l=0,r=all.size()-1;
    int mid;               // 二分法查找元素
    while(l<r){
        mid = (l+r)/2;
        if(all[mid]<x) l = mid + 1;
        else r = mid;
    }
    return l + 1;  // 前缀和矩阵下标从1开始
}

int main(){
    int n,m;
    cin >> n >> m;
    vector<PII> add;   // 存储加操作
    vector<PII> query; // 存储区间l,r
    vector<int> all;   // 对位置x,l,r进行映射
    int x,c,l,r;
    while(n--){
        cin >> x >> c;
        add.push_back({x,c}); 
        all.push_back(x); 
    }
    while(m--){
        cin >> l >> r;
        query.push_back({l,r});
        all.push_back(l);
        all.push_back(r);
    }
    sort(all.begin(),all.end()); // 排序
    all.erase(unique(all.begin(),all.end()),all.end()); // 去重,unique将重复元素放在数组末尾,并返回重复元素的起始位置
    for(auto item : add){
        x = find(item.first,all); // 位置x映射为相应下标
        a[x] += item.second;   // 操作数
    }
    for(int i=1;i<=all.size();i++) s[i] = s[i-1] + a[i]; // 求前缀和矩阵
    for(auto item : query){
        l = find(item.first,all);   // 区间l,r进行映射
        r = find(item.second,all);
        printf("%d\n",s[r]-s[l-1]);
    }
    return 0;
}

9.区间合并

使用贪心策略进行区间合并,其步骤如下:

1.首先对区间左端点进行排序,即:

2.对于前后相邻的2个区间,有两种关系,即有交集和无交集。若无交集,则当前区间即为独立的区间。若有交集,则进行合并,end=max(end,next_end)。

代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef pair<int,int> PII;

void q_merge(vector<PII> &a){
    sort(a.begin(),a.end());  // 以左端点为基准,对区间进行排序
    vector<PII> res;
    int st = -2e-9,ed = 2e-9; // 初始,区间的st和ed为负无穷
    for(auto item : a){
        if(ed < item.first){  // 当前区间和下一区间无交集.
           if(st!=-2e9) res.push_back({st,ed}); // st为负无穷代表初始化值
            st = item.first;  // 当前区间为独立区间,st,ed指向下一区间
            ed = item.second;
        }
        else{   // 当前区间与下一区间有交集,合并
            ed = max(ed,item.second);
        }
    }
    if(st!=-2e-9) res.push_back({st,ed}); // 最后一个区间进入数组
    a = res;
}

int main(){
    vector<PII> seg;
    int n,l,r;
    cin >> n;
    while(n--){
        cin >> l >> r;
        seg.push_back({l,r});
    }
    q_merge(seg);
    printf("%d",seg.size());
    return 0;
}

参考:AcWing

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/382013.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

visual studio和cmake如何编译dlib库

官网 dlib C Library 对应的是最新版本&#xff0c;只能用到vs2015版本及以后 如果使用vs2013&#xff0c;所以需要下载vs2013可用的版本。 就是说dlib版本与vs版本有对应关系 所有版本 dlib C Library - Browse /dlib at SourceForge.net Releases davisking/dlib GitHu…

[word] word如何打印背景和图片? #微信#其他#经验分享

word如何打印背景和图片&#xff1f; 日常办公中会经常要打印文件的&#xff0c;其实在文档的打印中也是有很多技巧的&#xff0c;可以按照自己的需求设定&#xff0c;下面给大家分享word如何打印背景和图片&#xff0c;一起来看看吧&#xff01; 1、打印背景和图片 在默认的…

C++笔记之regex(正则表达式)

C++笔记之regex(正则表达式) ——2024-02-10 ——《C++标准库》(第2版,侯捷译) Page 717 code review! 文章目录 C++笔记之regex(正则表达式)例1:使用正则表达式进行搜索(`std::regex_search`)例2:使用正则表达式进行全文匹配(`std::regex_match`)例3:使用正则表达式…

【JAVA WEB】 百度热榜实现 新闻页面 Chrome 调试工具

目录 百度热榜 新闻页面 Chrome 调试工具 --查看css属性 打开调试工具的方式 标签页含义 百度热榜 实现效果&#xff1a; 实现代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"vi…

Linux第45步_通过搭建“DNS服务器”学习图形化配置工具

学习的意义&#xff1a;通过搭建“DNS服务器”&#xff0c;来学习“图形化配置工具”。“DNS服务器”&#xff0c;我们用不到&#xff0c;但为后期移植linux系统服务&#xff0c;因为在移植系统时&#xff0c;需要用到这个“图形化配置工具”。 1、“menuconfig图形化配置工具…

Spring Boot 笔记 005 环境搭建

1.1 创建数据库和表&#xff08;略&#xff09; 2.1 创建Maven工程 2.2 补齐resource文件夹和application.yml文件 2.3 porn.xml中引入web,mybatis,mysql等依赖 2.3.1 引入springboot parent 2.3.2 删除junit 依赖--不能删&#xff0c;删了会报错 2.3.3 引入spring web依赖…

STM32F1 - 源码解析SystemInit()

SystemInit 1> SystemInit( )调用位置2> SystemInit ()函数2> SystemInit ()函数 1> SystemInit( )调用位置 startup_stm32f10x_hd.s文件中&#xff1a; ; Reset handler Reset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __mainIMPORT Sy…

LLM少样本示例的上下文学习在Text-to-SQL任务中的探索

导语 本文探索了如何通过各种提示设计策略&#xff0c;来增强大型语言模型&#xff08;LLMs&#xff09;在Few-shot In-context Learning中的文本到SQL转换能力。通过使用示例SQL查询的句法结构来检索演示示例&#xff0c;并选择同时追求多样性和相似性的示例可以提高性能&…

腾讯云4核8G12M轻量应用服务器性能够用吗?支持多少人?

腾讯云轻量4核8G12M轻量应用服务器支持多少人同时在线&#xff1f;通用型-4核8G-180G-2000G&#xff0c;2000GB月流量&#xff0c;系统盘为180GB SSD盘&#xff0c;12M公网带宽&#xff0c;下载速度峰值为1536KB/s&#xff0c;即1.5M/秒&#xff0c;假设网站内页平均大小为60KB…

【计算机网络】协议层次及其服务模型

协议栈&#xff08;protocol stack&#xff09; 物理层链路层网络层运输层应用层我们自顶向下&#xff0c;所以从应用层开始探究应用层 协议 HTTP 提供了WEB文档的请求和传送SMTP 提供电子邮件报文的传输FTP 提供两个端系统之间的文件传输报文&#xff08;message&#xff09;是…

《杨绛传:生活不易,保持优雅》读书摘录

目录 书简介 作者成就 书中内容摘录 良好的家世背景&#xff0c;书香门第为求学打基础 求学相关 念大学 清华研究生 自费英国留学 法国留学自学文学 战乱时期回国 当校长 当小学老师 创造话剧 支持钱锺书写《围城》 出任震旦女子文理学院的教授 接受清华大学的…

深入探索Java IO:从基础到高级操作全览

深入探索Java IO&#xff1a;从基础到高级操作全览 Java IO一、概览二、磁盘操作三、字节操作实现文件复制装饰者模式 四、字符操作编码与解码String 的编码方式Reader 与 Writer实现逐行输出文本文件的内容 五、对象操作序列化Serializabletransient 六、网络操作InetAddressU…

python3 获取某个文件夹所有的pdf文件表格提取表格并一起合并到excel文件

下面是一个完整的示例&#xff0c;其中包括了merge_tables_to_excel函数的定义&#xff0c;并且假设该函数的功能是从每个PDF文件中提取第一个表格并将其合并到一个Excel文件中&#xff1a; import os from pathlib import Path import pandas as pd import pdfplumber …

【数据分享】1929-2023年全球站点的逐日降水量数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;说到常用的降水数据&#xff0c;最详细的降水数据是具体到气象监测站点的降水数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全…

MYSQL存储过程(含入参、出参)

1、创建库存表语句 -- eladmin.t_stock definitionCREATE TABLE t_stock (id bigint(20) NOT NULL AUTO_INCREMENT,quantity bigint(20) NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT4101 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin; id为主键&#xff0c;便于…

【Java EE初阶十二】网络初识

1. 网络发展史 网络发展的几个主要时期&#xff1a; 单机时代->局域网时代->广域网时代->移动互联网时代 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#xff0c;即以多个计算机协同工作来完成 业务&#xff0c;就有了网络互…

基于 multiprocessing.dummy 的多线程池与单线程访问多网页的比较示例

一、示例代码&#xff1a; from multiprocessing.dummy import Pool as ThreadPool import time import requestsurls [ # URL队列&#xff0c;通过多线程访问http://www.python.org,http://www.python.org/about/,http://www.…

每日五道java面试题之java基础篇(二)

第一题. 为什么说 Java 语⾔“编译与解释并存”&#xff1f; ⾼级编程语⾔按照程序的执⾏⽅式分为编译型和解释型两种。 简单来说&#xff0c;编译型语⾔是指编译器针对特定的操作系统将源代码⼀次性翻译成可被该平台执⾏的机器码&#xff1b;解释型语⾔是指解释器对源程序逐…

【正在更新】从零开始认识语音识别:DNN-HMM混合系统语音识别(ASR)原理

摘要 | Abstract TO-BE-FILLED 1.前言 | Introduction 近期想深入了解语音识别(ASR)中隐马尔可夫模型(HMM)和深度神经网络-隐马尔可夫(DNN-HMM)混合模型&#xff0c;但是尽管网络上有许多关于DNN-HMM的介绍&#xff0c;如李宏毅教授的《深度学习人类语言处理》[1]&#xff0c;…

问题:超声波纵波斜入射时,当入射角大于第一临界角小于第二临界角时,在第二介质内只有折射横波。 #微信#经验分享#其他

问题&#xff1a;超声波纵波斜入射时&#xff0c;当入射角大于第一临界角小于第二临界角时&#xff0c;在第二介质内只有折射横波。 参考答案如图所示