【AcWing】基础算法

news2024/9/23 23:03:01

目录

1、快速排序

1.1 快速排序

1.2 第k个数

2、归并排序

2.1 归并排序

2.2 逆序对的数量

3、二分

3.1 数的范围

3.2 数的三次方根

4、高精度

4.1 高精度加法

4.2 高精度减法

4.3 高精度乘法

4.4 高精度除法

5、前缀和与差分

5.1 前缀和

5.2 子矩阵的和

5.3 差分

5.4 差分矩阵

6、双指针算法

6.1 最长连续不重复的子序列

6.2 数组元素的目标和

6.3 判断子序列

7、位运算

8、离散化

9、区间合并


1、快速排序

1.1 快速排序

785. 快速排序 - AcWing题库

1. 确定分界点:q[l]、q[r]、q[l + r >> 2]、随机

2. 调整区间

我们要将区间调整为下面这样子

当q[i] < x时,i++,当q[i] >= x时,i停止。当q[j] > x时,j--,当q[j] <= x时,j停止。判断一下此时是否i < j,若i < j则交换,否则就出循环。

此时为什么是正确的呢?

因为在任意时刻,i左边区间内的数都是小于等于x的,j右边区间内的数都是大于等于x的

3. 递归处理左右两段

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], n;

void quick_sort(int l, int r)
{
    if (l >= r) return ;
    int i = l - 1, j = r + 1, x = q[l];
    while (i < j)
    {
        while (q[ ++ i] < x)    ;
        while (q[ -- j] > x)    ;
        if (i < j)  swap(q[i], q[j]);
    }
    quick_sort(l, j);
    quick_sort(j + 1, r);
}

int main()
{
    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;
}

讲解代码中几个需要注意的地方:

1. 为什么while(i < j)里面的两个while循环不需要先判断i < j?

i < j是为了防止越界访问。我们先来看结束时,i和j的分布情况

(1) i和j可能会相遇在某一个x,此时i == j

(2) 因为i左边的数是<=x,j右边的数是>=x,所以i一定会停在j右边的区间的第一个数,j一定会停在i左边的区间的第一个数。最先开始的时候,i的左边没有数,j的右边没有数,会不会出现意外呢?不会的,以此时选取x=q[l]为例,一开始q[i] == x,i是不会动的,j会先动。若q[j] <= x,那么交换完q[i]和q[j]后,i++,j--,i的左边有值,j的右边有值。若q[j] > x,则j--找<=x的元素,找到后,两者一交换,左右就都不为空了。

2. 为什么一上来直接i++、j--?

因为无论是正常走,还是交换完,都需要让i++、j--。

注意:初始化时i = l - 1,j = r + 1

3. 递归时的区间选择

上面我们选取的分界点是x = q[l],递归时就是使用j,若选取的分界点是x = q[r],递归时就是使用i,否则就会出现死循环

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], n;

void quick_sort(int l, int r)
{
    if (l >= r) return ;
    int i = l - 1, j = r + 1, x = q[r];
    while (i < j)
    {
        while (q[ ++ i] < x)    ;
        while (q[ -- j] > x)    ;
        if (i < j)  swap(q[i], q[j]);
    }
    quick_sort(l, i - 1);
    quick_sort(i, r);
}

int main()
{
    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;
}

所以当选取的分界点是q[l]或q[r]时,要注意迭代区间的更新

当然,选取分界点时也可以直接使用随机数法或区中间值的方法

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], n;

void quick_sort(int l, int r)
{
    if (l >= r) return ;
    swap(q[l], q[l + rand() % (r - l + 1)]);
    int i = l - 1, j = r + 1, x = q[l];
    while (i < j)
    {
        while (q[ ++ i] < x)    ;
        while (q[ -- j] > x)    ;
        if (i < j)  swap(q[i], q[j]);
    }
    quick_sort(l, j);
    quick_sort(j + 1, r);
    //quick_sort(l, i - 1);
    //quick_sort(i, r); 也行
}

int main()
{
    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;
}

此时会有疑问,就是为什么此时分界点选择的是q[l],但是递归的时候可以用i呢?

实际上也是不行的,比如像5 4 1 3 2,若rand() == 2,变为1 4 5 3 2,若用i仍然会死循环,可以是因为rand()不一定是2 

这个算法相比较我们之前的快速排序算法是有优势的

void QuickSort(int* a, int left, int right)//Hoare法
{
	if (left >= right)
		return;
	int begin = left, end = right;
	//随机选key
	int randi = rand() % (right - left + 1);
	randi += begin;
	Swap(&a[left], &a[randi]);
	int keyi = left;
	while (begin < end)
	{
		while(begin < end && a[end] >= a[keyi])
			end--;
		while(begin < end && a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);
}

我们之前的算法在遇到全相等的情况时,时间复杂度是O(N^2),因为遇到==基准值时是会跳过去的,而现在的代码不会

1.2 第k个数

786. 第k个数 - AcWing题库

可以直接使用快速排序先将数组排序,然后再输出第k个数,时间复杂度是O(N*logN)

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int q[N],n,k;
void quick_sort(int l, int r)
{
    if(l >= r) return;
    swap(q[l], q[l + rand() % (r - l + 1)]);
    int i = l - 1,j = r + 1,x = q[l];
    while(i < j)
    {
        while(q[++i] < x) ;
        while(q[--j] > x) ;
        if(i < j) swap(q[i], q[j]);
    }
    quick_sort(l, j);
    quick_sort(j + 1, r);
}
int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 0;i < n;i++) scanf("%d", &q[i]);
    quick_sort(0, n - 1);
    printf("%d\n", q[k - 1]);
    return 0;
}

也可以使用快速选择算法

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int q[N],n,k;
int quick_sort(int l, int r,int k)
{
    if(l >= r) return q[l];
    int i = l - 1,j = r + 1,x = q[l];
    while(i < j)
    {
        while(q[++i] < x) ;
        while(q[--j] > x) ;
        if(i < j) swap(q[i], q[j]);
    }
    int sl = j - l + 1;
    if(sl >= k) return quick_sort(l, j, k);
    else return quick_sort(j + 1, r, k - sl);
}
int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 0;i < n;i++) scanf("%d", &q[i]);
    cout<<quick_sort(0, n - 1, k)<<endl;
    return 0;
}

这道题是保证第k个数在区间里面,所以区间里面至少有1个数,可以l==r作为递归结束条件,但是快速排序中一定要是l >= r,因为快速排序中l可能大于r 

2、归并排序

2.1 归并排序

787. 归并排序 - AcWing题库

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

2. 递归排序左右区间

3. 归并 ---- 将两个有序数组合二为一

注意:在这个步骤中,为了保证排序的稳定性,当两指针指向的值相等时,要将第一个数组的值放入临时数组

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], tmp[N], n;
void merge_sort(int l,int r)
{
    if(l >= r) return;
    int mid = (l + r) / 2;
    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++];
    }
    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(0, n - 1);
    for(int i = 0;i < n;i++) printf("%d ", q[i]);
    return 0;
}

2.2 逆序对的数量

788. 逆序对的数量 - AcWing题库

可以像归并排序时一样,将数组划分为两部分,此时逆序对会有3种情况

我们只需要一直向下递归,直到每个区间都只有一个值,就能都变成第三种情况

并且我们可以在归并的同时就行排序,因为当q[i] > q[j]时,则q[i]到q[mid]都是大于q[j]的

在"并"的过程中,利用两个有序数组计算逆序对的数量

此时q[i] > q[j],那么前半数组剩余的值都和q[j]构成逆序对

注意,逆序对的数量最多会大于int,所以开long long

#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int q[N], tmp[N], n;
ll merge_sort(int q[], int l, int r)
{
    if(l >= r) return 0;
    int mid = l + r >> 1;
    ll res = 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++];
            res += mid - i + 1;
        }
    }
    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
    for(int i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];
    return res;
}
int main()
{
    scanf("%d", &n);
    for(int i = 0;i < n;i++) scanf("%d", &q[i]);
    ll res = merge_sort(q, 0, n - 1);
    printf("%lld\n", res);
    return 0;
}

3、二分

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

作者:yxc
来源:AcWing

模板不需要特意去记什么时候在计算中点时要+1,只需要写出check后,根据更新判断 

为什么计算中点时有时候要+1,有时候不需要呢?

因为l + r >> 1是向下取整,当l = r - 1时,若chack为真,则没变,会死循环

3.1 数的范围

789. 数的范围 - AcWing题库

这道题是整数二分,分为找左端点和右端点

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, q, k;
int main()
{
    scanf("%d %d", &n, &q);
    for(int i = 0;i < n;i++) scanf("%d", &a[i]);
    while(q--)
    {
        scanf("%d", &k);
        // 先找左端点
        int l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(a[mid] < k) l = mid + 1;
            else r = mid;
        }
        if(a[l] != k) printf("-1 -1\n");
        else
        {
            // 找右端点
            printf("%d ", l);
            l = 0, r = n - 1;
            while(l < r)
            {
                int mid = l + r + 1 >> 1;
                if(a[mid] > k) r = mid - 1;
                else l = mid;
            }
            printf("%d\n", l);
        }
    }
    return 0;
}

3.2 数的三次方根

790. 数的三次方根 - AcWing题库

这道题是浮点数二分,初始时让l等于数的范围的左端点,r等于数的范围的右端点,一直二分,直到l和r之间的距离足够小即可。如何定义足够小呢?如果题目的浮点数保留6位小数,则while的循环条件时r - l > 1e-8

注意:浮点数二分不能直接取l=0,r=x,因为当0<x<1时,可能会出错,如求二次方跟时,x=0.01,而0.01的二次方根是0.1。在这道题中就不需要考虑这个,因为数的范围已经确定了

#include<iostream>
using namespace std;
int main()
{
    double n;
    scanf("%lf", &n);
    double l = -10000,r = 10000;
    while(r - l > 1e-8)
    {
        double mid = (r + l) / 2;
        if(mid * mid * mid < n) l = mid;
        else r = mid;
    }
    printf("%lf\n", l);
    return 0;
}

浮点数二分相对于整数二分更加简单,因为没有+1-1,所以不需要考虑边界问题 

4、高精度

这里的高精度是指大数之间的加减乘除运算

4.1 高精度加法

791. 高精度加法 - AcWing题库

高精度加法,A + B,通常A和B是大小在0到10^6的整数

大整数的存储通常使用数组来存储,并且最好是倒过来存储,方便进位

使用数组进行加法的过程就是模拟整数加法,Ci = Ai + Bi + t,t表示进位

#include<iostream>
#include<string>
#include<vector>
using namespace std;
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;
    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 = add(A, B);
    for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);
    return 0;
}

4.2 高精度减法

792. 高精度减法 - AcWing题库

高精度减法,A - B,通常A和B是大小在0到10^6的整数

因为一道题中结果可能很大,会使用到高精度的加减乘除,所以四种运算的存储方式相同

若Ai - Bi - t大于等于0,Ci = Ai - Bi - t

若Ai - Bi - t小于0,Ci = Ai - Bi - t + 10,t表示借位

#include<iostream>
#include<string>
#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.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(cmp(A, B)) // A >= B
    {
        vector<int> C = sub(A, B);
        for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);
    }
    else
    {
        vector<int> C = sub(B, A);
        printf("-");
        for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);
    }
    return 0;
}

sub中是默认A大于等于B的

(t + 10) % 10,当t >= 0时结果是不变的,当t < 0时,这个位置需要借位

还需要处理前导0,如789-786,此时A是9 8 7,B是6 8 7,减完之后C是3 0 0,反转过后也就是003,我们需要的是3,所以需要处理前导0 

4.3 高精度乘法

793. 高精度乘法 - AcWing题库

高精度乘法是一个很大的数字乘一个较小的数字,len(A)<=10^6,b<=10000

我们这里是将b当成一个整体,与A的每一位相乘

Ci = (Ai * b + t) % 10,t表示进位

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int b;
vector<int> mul(vector<int>& A, int b)
{
    int t = 0;
    vector<int> C;
    for(int i = 0;i < A.size() || t;i++) // 结束条件是A遍历完,并且t也为0
    {
        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(); // 处理前导0
    return C;
}
int main()
{
    vector<int> A;
    string a; cin>>a>>b;
    for(int i = a.size() - 1;i >= 0;i--) A.push_back(a[i] - '0');
    vector<int> C = mul(A, b);
    for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);
    return 0;
}

同样需要处理前导0,如A = [3, 2, 1],b = 0,结果是C = [0, 0, 0]

4.4 高精度除法

高精度除法通常A <= 10^6,b <= 10000

除法是从最高位开始计算的,而加减乘都是从最低位计算,为了保持统一,除法计算完reverse

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
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(); // 处理前导0
    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; // 余数
    vector<int> C = div(A, b, r);
    for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);
    printf("\n");
    printf("%d\n", r);
    return 0;
}

 同样需要处理前导0,如1234 / 11,A = [4, 3, 2, 1],b = 11,计算完后C = [0, 1, 1, 2],逆置完后C = [2, 1, 1, 0]

5、前缀和与差分

5.1 前缀和

795. 前缀和 - AcWing题库

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], dp[N], n, m, l, r;
int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1;i <= n;i++) 
    {
        scanf("%d", &q[i]);
        dp[i] = dp[i - 1] + q[i];
    }
    while(m--)
    {
        scanf("%d %d", &l, &r);
        printf("%d\n", dp[r] - dp[l - 1]);
    }
    return 0;
}

5.2 子矩阵的和

796. 子矩阵的和 - AcWing题库

#include<iostream>
using namespace std;
const int N = 1010;
int m, n, q, x1, y1, x2, y2;
int a[N][N], dp[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]);
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + a[i][j];
        }
    }
    while(q--)
    {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", dp[x2][y2] - dp[x2][y1 - 1] - dp[x1 - 1][y2] + dp[x1 - 1][y1 - 1]);
    }
    return 0;
}

5.3 差分

797. 差分 - AcWing题库

差分就是前缀和的逆过程

给定一个原数组a,我们要求差分数组b,使得a数组是b数组的前缀和,即a[i]=b[1]+b[2]+...+b[i]

1. 获得差分数组

很容易就能想到b[i] = a[i] - a[i - 1]

2. 使用差分数组

我们要对给定的区间[l, r]内的每个a数组加上c,按照正常做法,编译a数组的区间[l, r],每个数加上c,时间复杂度是O(m * n)。

此时就可以使用差分数组了。因为a数组是b数组的前缀和,所以我们会发现对a数组的区间[l, r]每个数加上c,实际上只需要对b[l] += c,b[r + 1] -= c

注意,因为我们对b进行了操作,但没对a操作,所以此时a数组名义上是b数组的前缀和,但是数值上已经不是了,所以m次操作完成后,要重新求一遍a

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

我们会发现,两步是可以使用同一个函数的。第二步是在a数组的区间[l, r]上插入值c。我们可以把第一步抽象为,假设a数组全为0,那么b数组也全为0,现在需要往a数组的[i, i]区间插入数值a[i],因为a数组并不是真的全为0,所以此时a数组既是b数组名义上的前缀和,也是数值上的前缀和

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

5.4 差分矩阵

798. 差分矩阵 - AcWing题库

这个就是二维差分,与前面的差分是类似的

#include<iostream>
using namespace std;
const int N = 1010;
int n, m, q, x1, y1, x2, y2, c;
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]);
            insert(i, j, i, j, a[i][j]);
        }
    }
    while(q--)
    {
        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++)
        {
            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");
    }
    return 0;
}

6、双指针算法

6.1 最长连续不重复的子序列

799. 最长连续不重复子序列 - AcWing题库

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], b[N];
int main()
{
    scanf("%d", &n);
    for(int i = 0;i < n;i++) scanf("%d", &a[i]);
    int ans = 0;
    for(int i = 0,j = 0;i < n;i++)
    {
        b[a[i]]++;
        while(b[a[i]] > 1)
        {
            b[a[j]]--;
            j++;
        }
        ans = max(ans, i - j + 1);
    }
    printf("%d", ans);
    return 0;
}

6.2 数组元素的目标和

800. 数组元素的目标和 - AcWing题库 

#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, m, x;
ll a[N], b[N];
int main()
{
    scanf("%d %d %d", &n, &m, &x);
    for(int i = 0;i < n;i++) scanf("%lld", &a[i]);
    for(int i = 0;i < m;i++) scanf("%lld", &b[i]);
    int i = 0,j = m - 1;
    while(i < n && j >= 0)
    {
        long long sum = a[i] + b[j];
        if(sum < x) i++;
        else if(sum > x) j--;
        else 
        {
            printf("%d %d\n", i, j);
            i++, j--;
        }
    }
    return 0;
}

6.3 判断子序列

2816. 判断子序列 - AcWing题库 

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
int main()
{
    scanf("%d %d", &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 j = 0;
    for(int i = 0;i < m;i++)
    {
        if(j >= n) break;
        if(a[j] == b[i]) j++;
    }
    if(j == n) printf("Yes");
    else printf("No");
    return 0;
}

7、位运算

801. 二进制中1的个数 - AcWing题库

位运算中,主要是两种问题

1. n的二进制表示中第k位是几  n >> k & 1,例如n=15,二进制是1010,左边是高位,右边是低位,下标是3210

2. lowbit(x):返回x的最后一位1,例如x=15,二进制是1010,lowbit(x)=10

lowbit是如何求的呢?x&-x = x&(~x+1)

lowbit可用于统计x的二进制表示中1的个数,每次将x中的一位1减掉,知道x变成0

使用方法一解题:

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], b[N];
int main()
{
    scanf("%d", &n);
    for(int i = 0;i < n;i++) scanf("%d", &a[i]);
    for(int i = 0;i < n;i++)
    {
        int sum = 0;
        for(int k = 0;k <= 31;k++)
            if((a[i] >> k & 1) == 1) sum++;
        b[i] = sum;
    }
    for(int i = 0;i < n;i++) printf("%d ", b[i]);
    return 0;
}

使用方法二解题:

// 方法二
int lowbit(int x)
{
    return x & -x;
}
int main()
{
    int n;
    scanf("%d", &n);
    while(n--)
    {
        int x;
        scanf("%d", &x);
        int res = 0;
        while(x) x -= lowbit(x), res++;
        cout<<res<<" ";
    }
    cout<<endl;
    return 0;
}

8、离散化

802. 区间和 - AcWing题库

这道题数轴上的数字是[-10^9, 10^9],一共是2*10^9个数,会进行n次插入和m次查询,每次查询会输入一个区间,n和m最大都是10^5,也就是说最多会用到3*10^5个数,这与2*10^9比起来是十分小的,如果我们要开一个2*10^9的数组,显然有很多空间是会浪费掉的。于是,这道题使用了离散化

因为值域太大,所以将用到的下标映射到从0开始的自然数(从1开始也行),映射的过程就称为离散化

我们先开一个数组,将用到的下标全部放进这个数组中,然后再进行离散化

离散化时要注意两个问题:
(1)用到的下标可能是存在重复的,所以要先去重
(2)如何将下标映射到从0开始的自然数?此时需要用到二分,利用其在存放数组中的下标,一个数映射后的值就是其在数组中的下标

#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]; // a是存离散化后的值的数组,s是存a的前缀和
vector<int> alls; // 存需要离散化的元素,x、l、r需要离散化,去重就是对alls去重
vector<PII> add, query; 
// add存要在x位置增加c的键值对
// query存每次询问的左右区间l和r
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;
}

 这里使用任何一个二分模板均可

9、区间合并

803. 区间合并 - AcWing题库

这道题就是首先将区间全部读入到一个vector<pair<int, int>>中,然后根据区间的左端点对区间进行排序,然后对所有区间进行遍历,遍历过程中将有交集的区间进行合并,合并好的区间放入一个新的数组中

遍历:维护当前区间,遍历后面的区间,看后面区间与当前区间的关系。因为已经按区间左端点进行排序了,所以关系只有3种

#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
int n;
vector<PII> segs, res; // segs读入所有区间,res存放合并好的区间
void merge(vector<PII>& segs)
{
    sort(segs.begin(), segs.end());
    int st = -2e9, ed = -2e9; // 最先开始让维护的区间位于最左边,并且第一次更新区间不能放入res
    for(int i = 0;i < n;i++)
    {
        if(ed < segs[i].first)
        {
            if(i != 0) res.push_back({st, ed});
            st = segs[i].first;
            ed = segs[i].second;
        }
        else ed = max(ed, segs[i].second);
    }
    if(st != -2e9) res.push_back({st, ed}); 
    // 最后还需要将最后维护的区间放入,此时要判断一下,因为有可能segs中没有任何区间
    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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0.设计模式总览——设计模式入门系列

在现代软件开发中&#xff0c;设计模式为我们提供了优秀的解决方案&#xff0c;帮助我们更好地组织代码和架构。本系列专栏将对设计模式的基本思想、原则&#xff0c;以及常用的分类、实现方式&#xff0c;案例对比、以及使用建议&#xff0c;旨在提高开发者对设计模式的理解和…

数据库的学习

第一章 绪论 基本概念 数据---描述事物的符号 数据库DB---长期存在在计算机内、有组织的、可共享的 大量数据的集合 数据库管理系统DBMS---位于用户和操作系统之间的一层数据管理软件 数据库系统DBS---由数据库、数据库管理系统&#xff08;及其应用开发工具&#xff09;、…

VBA技术资料MF201:添加简单的右键菜单

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

Linux bash特性:

命令别名&#xff1a; 命令行定义命令别名&#xff1a; alias cdt‘cd’ alias 别名‘完整命令’ unlias----删除别名 但是断开连接后&#xff0c;再次链接&#xff0c;命令别名会失效&#xff0c;不同窗口别名也不生效 配置文件内更改命令别名&#xff1a; 在对应用户的…

导入时,Excel模板不被下载

问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 这是个SSM项目&#xff0c;以前经常遇到这个问题&#xff0c;今天有幸记录下来 [ERROR][o.a.s.r.StreamResult] Can not find a java.io.InputStream with the name [downLoadFile] in the invocation stack…

【计算机网络篇】计算机网络概述

本文主要介绍计算机网络第一章节的内容&#xff0c;文中的内容是我认为的重点内容&#xff0c;并非所有。参考的教材是谢希仁老师编著的《计算机网络》第8版。跟学视频课为河南科技大学郑瑞娟老师所讲计网。 文章目录 &#x1f3af;一.计算机网络的组成 ✨主要内容 1.边缘部…

操作系统笔记三

进程 把一个静态程序通过OS在内存中让cpu执行起来的动态执行过程叫进程 写代码都是用户态&#xff0c;而进程在执行过程中需要完成特定的功能&#xff0c;这些功能呢只有操作系统能提供&#xff0c;比如说读写文件&#xff0c;读写文件的过程是与硬盘打交道&#xff0c;这个过程…

106.游戏安全项目-机制插件分析技巧-指针扫描

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信…

【初阶数据结构】详解二叉树 - 树和二叉树(三)(递归的魅力时刻)

文章目录 前言1. 二叉树链式结构的意义2. 手搓一棵二叉树3. 二叉树的遍历&#xff08;重要&#xff09;3.1 遍历的规则3.2 先序遍历3.3 中序遍历3.4 后序遍历3.5 遍历的代码实现3.5.1 先序遍历代码实现3.5.2 中序遍历代码实现3.5.3 后序遍历代码实现 4. 统计二叉树结点的个数5.…

Python 函数用法与底层分析

在编写函数时&#xff0c;函数体中的代码写法和我们前面讲述的基本一致&#xff0c;只是对代码实现了封装&#xff0c;并增加了函数调用、传递参数、返回计算结果等内容。 函数简介函数(function)的基本概念 1&#xff1a;一个程序由一个一个的任务组成&#xff1b;函数就是代…

《CUDA编程》1.GPU硬件与CUDA环境搭建

1 GPU 介绍 GPU&#xff08;graphics processing unit&#xff09;&#xff0c;意为图形处理器&#xff0c;也被称为显卡&#xff08;graphics card&#xff09;。GPU的浮点数运算峰值就比同时期的CPU高一个量级&#xff1b;GPU的内存带宽峰值也比同时期的CPU高一个量级。 CP…

【重学 MySQL】三十一、字符串函数

【重学 MySQL】三十一、字符串函数 函数名称用法描述ASCII(S)返回字符串S中的第一个字符的ASCII码值CHAR_LENGTH(s)返回字符串s的字符数&#xff0c;与CHARACTER_LENGTH(s)相同LENGTH(s)返回字符串s的字节数&#xff0c;和字符集有关CONCAT(s1,s2,…,sn)连接s1,s2,…,sn为一个字…

数据加密和数字证书

1 什么是数据加密 数据加密的基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,通常称为"密文",使其只能在输入相应的密钥之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。 该过程的逆过程…

【开源免费】基于SpringBoot+Vue.JS服装商城系统(JAVA毕业设计)

本文项目编号 T 046 &#xff0c;文末自助获取源码 \color{red}{T046&#xff0c;文末自助获取源码} T046&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

数据结构篇--折半查找【详解】

折半查找也叫做二分查找或者对数查找&#xff0c;是一种在有序数组中查找特定元素的查找算法。 折半查找的算法步骤如下&#xff1a; 将目标关键字key与数组中的中间元素比较&#xff0c;若相等则查找成功。key大于中间元素&#xff0c;就到数组中大于中间元素的部分进行查找&…

c++语法(模板初阶+类和对象部分知识点)

1:泛型编程 2:模板 2.1:函数模板 2.2:类模板 3:const成员函数与非const的区别 4:构造函数之初始化列表 4.1:初始化列表语法及其应用 4.2:explicit关键字 5:static成员变量,static成员函数 1:泛型编程 首先在提出泛型编程前我们先来看一下的代码(关于swap函数)。 void swap…

FPGA随记——VIVADO中ASYNC_REG指令

参考文章&#xff1a;Vivado综合属性系列一、ASYNC_REG_asyncregtrue-CSDN博客 -很棒棒的 跨时钟域设计&#xff08;CDC&#xff09;是个老生常谈的问题&#xff0c;其场景很多很杂&#xff0c;其中一个比较为人熟知的就是单bit信号从慢时钟到快时钟所采用的两级寄存器处理的…

抖音矩阵系统源码搭建短视频批量剪辑矩阵分发,可开源或oem

打造多语言短视频平台&#xff1a;技术实施方案揭秘 在短视频矩阵系统技术开发实施方案中&#xff0c;数据库设计是基础环节。首先&#xff0c;需要建立语言包数据库表并填充初始文本数据&#xff0c;如英语和中文的常用语。接着&#xff0c;要设计高效的数据库连接和数据访问接…

erlang学习:Linux命令学习3

shell基本输出 创建一个test.sh文件&#xff0c;并开放他的权限&#xff0c;之后向其中编辑以下内容 touch test.sh chmod 777 test.sh vim test.shecho "hello linux"之后运行相应shell程序得到输出 ./test.sh变量 单引号特点&#xff1a; 单引号里的任何字符都…

【Joint Receiver Design for ISAC】Neyman person | Gaussian | MMSE estimator |

【1】统计信号处理 Neyman-Pearson criterion pp 425 【1】 R c E { g x ( n ) x ( n ) H g H } σ 2 I g g H σ 2 I , \mathbf{R}_c\mathbf{E}\{\mathbf{g}x(n)x(n)^H\mathbf{g}^H\}\sigma^2\mathbf{I}\mathbf{g}\mathbf{g}^H\sigma^2\mathbf{I}, Rc​E{gx(n)x(n)HgH}σ2…