【C++】算法基础篇---基础算法

1.排序

1.1 模板

(1)快速排序

基于分治法,不稳定的,时间复杂度O(NlogN)

a.确定分界点  可以选择         q[ l ]   q[ ( l + r ) / 2 ]     q[ r ]       随机

b.调整区件    小于等于x的在左边    大于等于x的在右边

c.递归处理左右两边

----------------------------第一种----------------------------
class Solution {
public:
    void quick_sort(vector<int>& nums,int l,int r){
        if(l>=r) return;
        int q=nums[l],i=l-1,j=r+1;
        while(i<j){
           do i++; while(nums[i]<q);
           do j--; while(nums[j]>q);
           if(i<j)swap(nums[i],nums[j]);
        }
        quick_sort(nums,l,j);
        quick_sort(nums,j+1,r);
    }
    vector<int> sortArray(vector<int>& nums) {
        quick_sort(nums,0,nums.size()-1);
        return nums;
    }
};
----------------------------第二种----------------------------
class Solution {
public:
    void quick_sort(vector<int>& nums,int l,int r){
        if(l>=r) return;
        int q=nums[l],i=l,j=r;
        while(i<j){
            while(nums[++i]<q);
            while(nums[--j]>q);
            if(i<j){
                swap(nums[i],nums[j]);
            }
        }
        quick_sort(nums,l,j);
        quick_sort(nums,j+1,r);
    }
    vector<int> sortArray(vector<int>& nums) {
        quick_sort(nums,0,nums.size()-1);
        return nums;
    }
};

可否变为稳定的:可以的,使用pair带上数的下标 

(2)归并排序

基于分治法,稳定的,时间复杂度O(NlogN)

a.确定分界点     mid=( l + r ) / 2

b.递归排序         left    right 

c.归并两边      合二为一

class Solution {
public:
    int tmp[100010];
    void merge_sort(vector<int>& nums,int l,int r){
        if(l>=r) return;
        int mid=(l+r)/2;;
        merge_sort(nums,l,mid);
        merge_sort(nums,mid+1,r);
        int k=0,i=l,j=mid+1;
        while(i<=mid&&j<=r){
            if(nums[i]<=nums[j]) tmp[k++]=nums[i++];
            else tmp[k++]=nums[j++];
        }
        while(i<=mid) tmp[k++]=nums[i++];
        while(j<=r) tmp[k++]=nums[j++];
        for(int i=l,j=0;i<=r;i++,j++){
            nums[i]=tmp[j];
        }
        
    }
    vector<int> sortArray(vector<int>& nums) {
        merge_sort(nums,0,nums.size()-1);
        return nums;
    }
};

1.2 例题

题目:

        给定一个长度为n的整数数列,以及一个整数k,请用快速选择算法求出数列的第k小的数是多少。

输入格式:
        第一行包含两个整数 n 和 k。第二行包含 n 个整数(所有整数均在1~109范围内),表示整数数列。

输出格式:
        输出一个整数,表示数列的第k小数。

数据范围:
        1≤n≤100000,1≤k≤n

输入样例:

5 3
2 4 1 5 3

输出样例:

3

解答:

#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int q[N];
int n,k;

int quick_k_sort(int l,int r,int k){
    if(l>=r) return q[l];
    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]);
    }
    int sl=j-l+1;
    if(k<=sl) return quick_k_sort(l,j,k);
    else return quick_k_sort(j+1,r,k-sl);
}
int main(){
    cin>>n>>k;
    for(int i=0;i<n;i++) cin>>q[i];
    cout<<quick_k_sort(0,n-1,k);
    return 0;
}

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

逆序对的定义如下:对于数列的第 i 个和第 j  个元素,如果满足 i < j且 a [ i ] > a [ j ] ,则其为一个逆序对;否则不是。

输入格式:

第一行包含整数 n nn,表示数列的长度。

第二行包含 n nn 个整数,表示整个数列。

输出格式:

输出一个整数,表示逆序对的个数。

数据范围:

1 ≤ n ≤ 100000 ,数列中的元素的取值范围 [ 1 , 10 ^9 ]

输入样例:

6
2 3 4 5 6 1

输出样例:

5

解答:

#include<iostream>
#include<algorithm>

using namespace std;
typedef long long LL;
const int N=100010;

int q[N],tmp[N];
int n;

LL merge_sort(int l,int r){
    if(l>=r) return 0;
    int mid= l + r >>1;
    LL res=merge_sort(l,mid)+merge_sort(mid+1,r);
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r){
        if(q[i]<q[j]) tmp[k++]=q[i++];
        else {
            tmp[k++]=q[j++];
            res+=mid-i+1;
        }
    }
    while(i<=mid) tmp[k++]=q[i++];
    while(j<=r) tmp[k++]=q[j++];
    
    for(int t=l,p=0;t<=r;t++,p++) q[t]=tmp[p];
    return res;
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>q[i];
    cout<<merge_sort(0,n-1);
    return 0;
}

2.二分查找

2.1  模板

算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。

2.1.1 整数排序

2.1.1.1 求一个数
int bsearch_left(int l, int r)
{
    while(l<r){
          mid=(l+r)>>1;
          if(q[mid]==goal) return mid;
          else if(q[mid]>goal) r=mid;
          else l=mid+1;
    }
    return -1;
}
2.1.1.2 左分界点

当我们将区间[ l ,  r ]划分成[ l ,  mid ]和[ mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。

int bsearch_left(int l, int r)
{
    while(l<r){
          mid=(l+r)>>1;
          if(check(mid)) r=mid; 
          else l=mid+1; 
    }
    return l;
}
2.1.1.3 右分界点

当我们将区间 [l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。

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

2.1.2 浮点数排序

double bsearch(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;
}

2.2 例题

题目描述:

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

输入格式:

        共一行,包含一个浮点数n

输出格式:

        共一行,包含一个浮点数,表示问题的解。

        注意,结果保留6位小数。

数据范围:  −10000 ≤ n ≤10000

输入样例:

1000.00 

输出样例:

10.000000 

解答:

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

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

3.高精度

3.1 存储与输入输出

输入

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

输出

vector<int> C;
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);

注意事项:

高精度加法:

  • 倒叙输入,倒序输出,但中间是正着运算;
  • 相加之后的进位处理;

高精度减法:

  • 考虑结果出现的正情况;
  • 前导0的处理;
  • 考虑减法的借位处理;

高精度乘法:

  • 前导0的处理(0被相乘);
  • 可以将乘法单个位数相乘再转化成加法的思想;
  • 此时题目中没有涉及到负数的情况。如出现负数,只需考虑两个字符串第一位是否为负号,然后结尾特殊判断一下即可;

高精度除法:(高精度÷低精度)

  • 输入、计算、输出、需要同时逆序或同时正序;
  • 前导0的处理;
  • 不能考虑进位的情况

3.2 高精度加法

3.2.1 模板

大数A,B读入时使用字符串string读入,然后从各位开始按序放入整数数组中,注意存入整数需要减掉‘0’

// C = A + B, A >= 0, B >= 0
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(t);
    return C;
}

3.2.2 例题 

题目:

给定两个正整数(不含前导 0),计算它们的和。

输入格式:

共两行,每行包含一个整数。

输出格式:

共一行,包含所求的和。

数据范围:

1≤整数长度≤100000

输入样例:

12
23

输出样例:

35

解答:

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

const int N = 100010;
vector<int> A , B;
 
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(t);
    return C;
}
int main()
{
	string n, m;
	cin >> n >> m;
	for (int i = n.size()-1; i >=0; i--)	A.push_back(n[i]-'0');
    for (int i = m.size()-1; i >=0; i--)	B.push_back(m[i]-'0');
	vector<int> C=add(A,B);
	for(int i = C.size()-1; i >=0; i--) cout<<C[i];
	return 0;
}

3.3 高精度减法

3.3.1 模板

// 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;
    }
    //如果答案不为0,则删去前导0

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

3.3.2 例题

题目:
给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。

输入格式:
共两行,每行包含一个整数。

输出格式:
共一行,包含所求的差。

数据范围:
1≤整数长度≤100000

输入样例:

32
11

输出样例:

21 

解答:

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

const int N = 100010;
vector<int> 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;
}

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

int main()
{
	string n, m;
	cin >> n >> m;
	for (int i = n.size()-1; i >=0; i--)	A.push_back(n[i]-'0');
    for (int i = m.size()-1; i >=0; i--)	B.push_back(m[i]-'0');
    vector<int> C;
    if(cmp(A,B)){
        C=sub(A,B);
    }else{
        C=sub(B,A);
        cout<<"-";
    }
	
	for(int i = C.size()-1; i >=0; i--) cout<<C[i];
	return 0;
}

3.4 高精度乘低精度

3.4.1 模板

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

3.4.2 例题

题目:

给定两个非负整数(不含前导 0) A 和 B,请你计算 A×B的值。

输入格式:
共两行,第一行包含整数 A,第二行包含整数 B。

输出格式:
共一行,包含 A×BA×B 的值。

数据范围:
1≤A的长度≤100000  0≤B≤100000 

输入样例:

2
3

输出样例:

6

解答:

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


const int N = 100010;
vector<int> A , B;


vector<int> mul(vector<int> &A,int b){
    vector<int> C;
    int t=0;//进位位
    for(int i=0;i<A.size()||t;i++){
        //当数字处理完且进位t也处理完后即可结束循环
        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 n;
	int m;
	cin >> n >> m;
	for (int i = n.size()-1; i >=0; i--)	A.push_back(n[i]-'0');
    vector<int> C=mul(A,m);
	for(int i = C.size()-1; i >=0; i--) cout<<C[i];
	return 0;
}

3.5 高精度除以低精度

3.5.1 模板

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

3.5.2 例题

题目:
给定两个非负整数(不含前导 0)A ,B ,请你计算 A / B 的商和余数。

输入格式:
共两行,第一行包含整数 A,第二行包含整数B。

输出格式:
共两行,第一行输出所求的商,第二行输出所求余数。

数据范围:
1 ≤ A 的长度 ≤ 100000 ,1 ≤ B ≤ 10000 ,B 一定不为 0
 

输入样例:

7
2

输出样例:

3
1

解答:

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


const int N = 100010;
vector<int> A , B;


vector<int> div(vector<int> &A,int b,int &r){
    vector<int> C;

    for(int i=A.size()-1;i>=0;i--){
        //当数字处理完且进位t也处理完后即可结束循环
        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 n;
	int b,r=0;
	cin >> n >> b;
	for (int i = n.size()-1; i >=0; i--)	A.push_back(n[i]-'0');
    vector<int> C=div(A,b,r);
	for(int i = C.size()-1; i >=0; i--) cout<<C[i];
	cout<<endl<<r<<endl;
	return 0;
}

4.前缀和和差分

前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)-优快云博客

前缀和问题,一般下标从1开始,为了减一之后不会越界

4.1 一维前缀和

4.1.1 模板

原数为  a1  a2  a3   a4   a5…

前缀和  Si=a1+a2+a3+…+ai

求区间前缀和[l,r]

S_{r}- S_{l-1}

前缀和:S[i] = a[1] + a[2] + ... a[i]=S[i-1]+a[i]
部分和:a[l] + ... + a[r] = S[r] - S[l - 1]

4.1.2 例题

 题目:

输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式:
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式:
共m行,每行输出一个询问的结果。
数据范围:
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000

输入样例:

5 3
2 1 3 6 4
1 2
1 3
2 4

输出样例:

3
6
10

解答:

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

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

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>q[i];
    for(int i=1;i<=n;i++) S[i]=S[i-1]+q[i];
    while(m--){
        int l,r;
        cin>>l>>r;
        cout<<S[r]-S[l-1]<<endl;
    }
    return 0;
}

4.2 二维前缀和

4.2.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]
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
部分和:S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

首先给定一个原数组a:a[1], a[2], a[3],,,,,, a[n];

然后我们构造一个数组b : b[1], b[2], b[3],,,,,, b[i];

使得 a[i] = b[1] + b[2] + b[3] + ,,,,,, + b[i]

也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。

4.2.2 例题

 题目:

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

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

输入格式:
第一行包含三个整数n,m,q。

接下来n行,每行包含m个整数,表示整数矩阵。

接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。

输出格式:

共q行,每行输出一个询问的结果。

数据范围:
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:

17
27
21

解答:

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

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

int main(){
    int n,m,q;
    cin>>n>>m>>q;
    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]-S[i-1][j-1]+a[i][j];
            
    while(q--){
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        cout<<S[x2][y2]-S[x1-1][y2]-S[x2][y1-1]+S[x1-1][y1-1]<<endl;
    }
    
    return 0;
}

4.3 一维差分

4.3.1 模板

给区间[l, r]中的每个数加上c:
                    B[l] += c, B[r + 1] -= c

4.3.2 例题

题目:

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

输入格式:
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。
输出格式:
共一行,包含n个整数,表示最终序列。
数据范围:
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000

输入样例:

6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

输出样例:

3 4 5 3 4 2

解答:

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

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

void insert(int l,int r ,int c){
    b[l]+=c;
    b[r+1]-=c;
}

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
            
    for(int i=1;i<=n;i++) insert(i,i,a[i]);
            
    while(m--){
        int l,r,c;
        cin>>l>>r>>c;
        insert(l,r,c);
    }

    for(int i=1;i<=n;i++){
        a[i]=a[i-1]+b[i];
        cout<<a[i]<<" ";
    }
    return 0;
}

4.4 二维差分

4.4.1 模板

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

4.4.2 例题

题目:

输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上c。
请你将进行完所有操作后的矩阵输出。
输入格式:
第一行包含整数n, m, q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。
输出格式:
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围:
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2

解答:

#include <bits/stdc++.h>
using namespace std;
 
const int N = 1010;
int a[N][N] ,S[N][N] = {};
 
int main() {
    int n,m,q;
    cin>>n>>m>>q;
 
    int i, j;
    for (i=1; i<=n; i++) {
        for (j=1; j<=m; j++) {
            cin>>a[i][j];
            S[i][j] = a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]; 
        }
    }
 
    for (i=0; i<q; i++) {
        int x1, y1, x2, y2, c;
        cin>>x1>>y1>>x2>>y2>>c;
        S[x1][y1] += c;
        S[x1][y2+1] -=c;
        S[x2+1][y1] -=c;
        S[x2+1][y2+1] += c;
    }
 
    for (i=1; i<=n; i++) {
        for (j=1; j<=m; j++) {
            S[i][j] += S[i-1][j]+S[i][j-1]-S[i-1][j-1];
            printf("%d ", S[i][j]);
        }
        cout<<endl;;
    }
 
    return 0;
}

5.双指针

5.1 模板

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

    // 具体问题的逻辑
}

核心思想:使用双指针算法是可以把暴利双循环算法优化到O(N)

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

5.2 例题

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

数据保证有唯一解。

输入格式:
第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。

第二行包含n个整数,表示数组A。

第三行包含m个整数,表示数组B。

输出格式:
共一行,包含两个整数 i 和 j。

数据范围:
数组长度不超过100000。
同一数组内元素各不相同。
1≤数组元素≤10^9

输入样例:

4 5 6
1 2 4 7
3 4 6 8 9

输出样例:

1 1
-----------------------暴力解法------------------------
#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=100010;
int A[N],B[N];
int l,r;

int main(){
    int n,m,x;
    cin>>n>>m>>x;
    for(int i=0;i<n;i++) 
       cin>>A[i];
            
    for(int i=0;i<m;i++) 
       cin>>B[i];
      
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++){
            if(A[i]+B[j]==x){
                l=i;
                r=j;
            }
        }
    cout<<l<<" "<<r;
    return 0;
}

---------------------双指针-------------------------
#include<iostream>
#include<cstdio>
#include<algorithm>

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++) 
       cin>>A[i];
            
    for(int i=0;i<m;i++) 
       cin>>B[i];
      
    for(int i=0,j=m-1;i<n;i++){
        while(j>=0 && A[i]+B[j]>x) j--;
        if(A[i]+B[j]==x)  {
            cout<<i<<" "<<j;
            break;
        }
    }
    
    return 0;
}

6.位运算

6.1 n的第k位

a.先把第k位右移到最后一位

b.和只有最后一位为1的1进行“与”运算

n>>k&&1

6.2 lowbit操作

lowbit(x)=x&-x--------(-x相当于~x+1)

有多少个1,可以采用

while(x){

        x-=lowbit(x);

        sum++;

}

7.离散化

7.1模板

        给出一列数字,在有些情况下,这些数字的值的绝对大小不重要,而相对大小很重要。具体地说,我们将原始数据排序,然后为每个不同的值分配一个整数。这个整数是该值在排序后出现的位置,即离散化后的数值。

        “离散化”就是用数字的相对值替代它们的绝对值。离散化是一种数据处理的技巧,它把分布广而稀疏的数据转换为密集分布,从而能够让算法更快速、更省空间地处理。

假设有一个值域很大(如0≤x≤10^9)但数量很少(如0<n≤10^5)的数列a。离散化步骤如下。

  • 排序。首先需要对数列排序,排序后才能确定相对大小。
  • 离散化。把排序后的数列元素从1开始逐个分配数值,完成离散化。
  • 归位。把离散化后的每个元素放回原始位置,结束。

问题:

(1)a[n]中可能有重复元素    ----去重

(2)如何快速映射,算出a[i]离散化后的值       ----二分

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
}

 7.2 例题

题目:

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

输入格式:
第一行包含两个整数 n  和 m 。
接下来 n 行,每行包含两个整数 x 和 c 。
再接下来 m行,每行包含两个整数 l 和 r 。

输出格式:
共 m 行,每行输出一个询问中所求的区间内数字和。

数据范围:

输入样例:

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:

8
0
5
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

typedef pair<int, int> PII;
const int N = 3 * 1e5 + 10;
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 - l) >> 1);
		if (alls[mid] >= x) r = mid;
		else l = mid + 1;
	}
	return l + 1; // 由于S是从1开始的,所以对应映射位置都往前提一位
}
//二分
int find2(int x){ // 用 lower_bound(), 找到第一个大于等于x的位置(其实也是二分)
    return lower_bound((alls.begin(), alls.end()), x)-alls.begin()+1;
}
int main()
{
	int n, m;
	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.区间合并

8.1 模板

通常区间合并是给定数个区间,想要将有交集的区间合并成一个区间

a.按左端点进行排序

b.进行合并---三种情况(包含、相交、相接)

// 将所有存在交集的区间合并
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;
}

8.2 例题

输入样例:

5
1 2
2 4
5 6
7 8
7 9

 输出样例:

3

解答:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N= 1e5+10;
int st[N],ed[N];  //起点和终点 
int n=0;
//成对出现的数据,对组可以返回两个数据 
typedef pair<int,int> PII; //将 pair<int, int> 定义为 PII 类型的别名 
vector<PII> segs; //创建一个存放区间的数组 
	
void merge(vector<PII> &segs)  //合并区间 
{
	vector<PII> res; //这是答案
	
	sort(segs.begin(),segs.end()); //先给所有的区间从小到大排序,pair排序优先排左端点,再右端点
	
	int st=-2e9,ed=-2e9;  //初始的边界,后面用来纪录当前区间的坐标 
	for(auto seg:segs)  //c++11,seg是循环变量,用于依次表示数组segs中的每个元素 
	{
		if(ed<seg.first) //当前区间右端点小于枚举的区间的左端点,说明找到了一个新的区间 
		{
			if(st!=-2e9) res.push_back({st,ed});  //当st不为初始值时,区间放进答案,实际上题目里的数据全部都不为初始值
			st=seg.first,ed=seg.second;  //更新区间
		}
		else  //除此之外的情况都是有交集的 
		{
			ed=max(ed,seg.second);  //把右端点更新成更右的那一个 
		}
	} 
	res.push_back({st,ed}); //把最后的合并区间加入数组
	//这个最后的合并区间是因为一直在更新右端点而没有触发前面的找到新区间的push_back而存在的,或者是最后枚举的区间与当前区间无交集,此时当前区间被更新为枚举的最后一个区间,但是还没有存入数组之中 
	
	segs=res;  //把原来传入的区间数组更新 
}	
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		int l,r;
		cin>>l>>r;   //输入一个区间
		segs.push_back({l,r}); //向容器末尾添加一个元素,把区间放进segs数组中 
	}
	merge(segs);  //区间合并
	cout<<segs.size()<<endl; //最后数组里有多少个区间,就是合并后的区间数量 
	return 0;
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值