Acwing基础算法

基础算法(2022-02-13)

一.♥♥算法基础题目

1.基础算法

  • AcWing 785. 快速排序
  • AcWing 786. 第k个数
  • AcWing 787. 归并排序
  • AcWing 788. 逆序对的数量
  • AcWing 789. 数的范围
  • AcWing 790. 数的三次方根
  • AcWing 791. 高精度加法
  • AcWing 792. 高精度减法
  • AcWing 793. 高精度乘法
  • AcWing 794. 高精度除法
  • AcWing 795. 前缀和
  • AcWing 796. 子矩阵的和
  • AcWing 797. 差分
  • AcWing 798. 差分矩阵
  • AcWing 799. 最长连续不重复子序列
  • AcWing 800. 数组元素的目标和
  • AcWing 801. 二进制中1的个数
  • AcWing 802. 区间和
  • AcWing 803. 区间合并

二.基础算法

1.排序

1.1 快速排序算法模板

题目:

题目:
给定你一个长度为n的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在1~10^9范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

题解:

快速排序-----分治思想  O(nlogn)
 1.确定分界点q[l],q[l+r/2],q[r],随机一个都可以当作分界点
 2.调整区间以x为分界,让<=x的在左边;>=x的在右边
 解1:
    2.1 两个数组a[],b[]
    2.2 q[l--r]     q[i]<=x     x---->a[]
                    q[i]>=x     x---->b[]
    2.3 a[]放q[],b[]放q[]
 解2:(双指针):
    左边i,右边j;i左边所有的数都<=x,j右边数都是>=x
    
 3.递归处理左右两段左右排序再拼一起

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n;
int q[N];

void quick_sort(int q[], int l, int r)
{
    //只有一个数或者没有数的时候直接return
    if (l>=r) return;
    //边界的左右两边
    //第一次的时候i和j都向两边移动所以i和j的初始值为i=l-1,j=r+1
    int i=l-1,j=r+1,x=q[l+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]);
    }
    quick_sort(q,l,j), quick_sort(q,j+1,r);
}
/*
//递归是j的话不能取到q[r]
void quick_sort(int q[],int l,int r){
    if(l>=r) return;
    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]);
    }
    quick_sort(q,l,j);
    quick_sort(q,j+1,r);
}


//递归用i的话,边界不能取到q[l],会死循环  eg.1 2的时候
void quick_sort(int q[],int l,int r){
    if(l>=r) return;
    int x=q[(l+r+1)/2],i=l-1,j=r+1;
    //int x=q[(l+r)/2],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]);
    }
    quick_sort(q,l,i-1);
    quick_sort(q,i,r);
}
*/
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;
}
(1)例题1

P1177 【模板】快速排序

(2)例题2快速选择

题目:

题目:
给定一个长度为n的整数数列,以及一个整数k,
请用快速选择算法求出数列的第k小的数是多少。
输入格式:
第一行包含两个整数 n和k。
第二行包含n个整数(所有整数均在1~109范围内),表示整数数列。
输出格式:
输出一个整数,表示数列的第k小数。
数据范围:
1≤n≤100000,1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3

题解:

一、快排:O(NlogN)
 快速排序-----分治思想  O(nlogn)
 1.确定分界点q[l],q[l+r/2],q[r],随机
 2.调整区间以x为分界,让Left<=x在左边;Right>=x在右边
 3.递归处理左右两段左右排序再拼一起
 二、快速选择:O(N)
 1.k<=Sl,递归Left
 2.k>Sl,递归Right,k-Sl
 sl左边数的个数

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,k;
int q[N];

int quick_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){
        while(q[++i]<x);
        while(q[--j]>x);
        if(i<j) swap(q[i],q[j]);
    }
    //左边Left的数l----j----r
    int sl=j-l+1;
    if(k<=sl) return quick_sort(l,j,k);
    //找第K个数,左边sl个数,右边k-sl个数
    return quick_sort(j+1,r,k-sl);
}

int main(){
    cin>>n>>k;
    for(int i=0;i<n;i++) cin>>q[i];
    cout<<quick_sort(0,n-1,k)<<endl;
}
1.2 归并排序算法模板

题目:

题目描述:
给定你一个长度为n的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在1~10^9范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

题解:

归并排序---分治(稳定排序)  O(nlogn)
1.以数组下标中间分开为左边和右边:mid=(l+r)/2
2.递归排序left,right
3.归并合二为一    
    双指针
    ----------
    i  1 3 5 7 9 
    j  2 4 5 8 10
    ----------
    比较a[i],b[j]大小,小的放进res[];
    若a[i]<b[j]则a[i]放res[],i++;
    若a[i]>b[j]则b[j]放res[],j++;
    若a[i]==a[b],则两个任意一个都可以放进res[]
    i先完后面把b[j]后面加res[];
    j先完后面把a[i]后面加res[];

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n;
int a[N],temp[N];   //temp[]为辅助空间
void merge_sort(int a[],int l,int r){
    //1个或者0个数
    if(l>=r) return;
    
    //(1)确定中间点
    int mid=l+r>>1;
    //(2)递归排序
    merge_sort(a,l,mid);
    merge_sort(a,mid+1,r);

    //(3)归并
    //k表示当前合并了几个数了,也就是temp里面有几个数了;i,j是两指针起点
    int k=0,i=l,j=mid+1;    
    while(i<=mid && j<=r){
        if(a[i]<a[j]) temp[k++]=a[i++];
        else temp[k++]=a[j++];
    }
    //左边没有循环完
    while(i<=mid) temp[k++]=a[i++];
    //右边没有循环完
    while(j<=r) temp[k++]=a[j++];

    for(i=l,j=0;i<=r;i++,j++){
        a[i]=temp[j];
    }
}
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    merge_sort(a,0,n-1);
    for(int i=0;i<n;i++) printf("%d ",a[i]);
    return 0;
}

2.二分

2.1 整数二分算法模板

题目:

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

输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。

输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回“-1 -1”。

数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1

题解:

二分的本质是边界二段性:
    在某区间定义了某一种性质,一边满足该性质,另外一边不满足该性质,则二分能找到满足该性质的边界
      
一.区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:mid在右边,找下边界  
    l-----mid(true)----下边界,上边界---mid(false)-----r
        左半边不满足性质               右半边满足性质
1.mid=l+r+1>>1   if(check(mid))  1.1 true   要找的下边界在{mid,r}
											包含mid(mid在整个左半边区间都能取到可能是下边界)   											 更新 l=mid;  mid在下边界的左边
                                 1.2 false  要找的边界在{l,mid-1}不包含mid  
                                            更新 r=mid-1
2.eg:当l=r-1,若mid=l+r>>1,则此时mid=l,会死循环
             若mid=l+r+1>>1,则此时mid=r,正确
             
3.模板:找下边界(左边)--->l=mid
// 区间[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;   //l=mid的情况,中间点的更新方式是m=(l+r+1)/2
        else r = mid - 1;
    }
    return l;
}

二.区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:mid在左边,找上边界  
    l-----mid(false)----下边界,上边界---mid(true)-----r
          左半边不满足性质              右半边满足性质           
1.mid=l+r>>1    if(check(mid))   1.1 true    要找的上边界在{l,mid}包含mid    
                                             更新 r=mid ;mid在上边界的右边
                                 1.2 false   要找的边界在{mid+1,r}不包含mid 
                                             更新 l=mid+1
2.模板:找上边界(右边)--->r=mid
// 区间[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;    // r=mid的情况,中间点的更新方式是m=(l+r)/2
        else l = mid + 1;
    }
    return l;
}
需要写两个二分,一个需要找到>=x的第一个数,另一个需要找到<=x的最后一个数。
1.查找不小于x的第一个位置-->找上边界(右边)--->l---x---mid(true)----r
2.查找不大于x的最后一个位置-->找下边界(左边)

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
int a[N];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    while(m--){
        int x;
        scanf("%d",&x);
        int l=0,r=n-1;
        //二分起始坐标
        //相当于找x的上边界在右边
        while(l<r){
            int mid=l+r>>1;
            //a[0]---x---mid(true)--a[l-1]如果满足的话check则mid在边界x(要找的数)的右边
            //当a[mid] >= x时,说明mid及其左边可能含有值为x的元素
            //当a[mid]小于x时,令l = mid + 1,mid及其左边的位置被排除了,可能出现解的位置是mid + 1及其后面的位置;
            if(a[mid]>=x) r=mid;
            else l=mid+1;
        }
        //上面的while结束时候l==r
        if(a[l]!=x)  cout<<"-1 -1"<<endl;   
        else{
            //相当于在左边
            cout<<l<<" ";
            int l=0,r=n-1;
            while(l<r){
               int mid=l+r+1>>1;
                //a[0]---mid(true)---x--a[l-1]如果满足的话check则mid在边界x(要找的数)的左边
               if(a[mid]<=x) l=mid;  
               else r=mid-1;
            }
            cout<<l<<endl;
        }
    }
    return 0;
}
2.2 浮点数二分算法模板

题目:

求一个数的开平方根

题解:

1.直接迭代一百次
  while(right - left > eps)   { ... }
  或者:
  for(int i = 0; i < 100; i++) { ... }

2.求误差在某一个范围(比要求的经度值大2)
const double eps =1e-7;        //精度eps
while(r - l> eps){     
      double mid = l+r>>1;
      if (check(mid)) r = mid;           //判定,然后继续二分
      else            l = mid;
}
或者:
for(int i = 0; i < 100; i++) {
	  double mid = l+r>>1;
      if (check(mid)) r = mid;           //判定,然后继续二分
      else            l = mid;
}

代码:

#include <bits/stdc++.h>
using namespace std;
int main(){
    double x;
    cin>>x;
    double l=0,r=x;
    //比要求的经度值大2
    while(r-l>1e-8){
        double mid=(l+r)/2;
        if(mid*mid>=x) r=mid;
        else l=mid;
    }
    /*
    或者直接迭代一百次
    for(int i=0;i<100;i++){
        double mid=(l+r)/2;
        if(mid*mid>=x) r=mid;
        else l=mid;
    }*/
    printf("%lf\n",l);
    return 0;
}
ps:推荐博客1 推荐博客2

3.高精度

3.1 几种常见的情况:
(1)A+B 位数1e6
(2)A-B 位数1e6
(3)A*b len(A)<=1e6 ,b<=1e9
(4)A/B
3.2 步骤:
(1)大整数存储(逆序存储)
(2)计算
3.3 高精度加法

题目:

题目:
现在有一个简单的问题,给你两个正整数A和B,你需要计算出A+B 的结果。不过要注意哦,这两个正整数非常大。
输入描述:
输入两个正整数A和 B ,A和 B的位数不超过 1000000。
输出描述:
输出 A+B,结果占一行。
数据范围:
1<=参数长度<=1e6;

题解:

和我们笔算加法时一样的思路,注意数据存储的时候时倒着存储的

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;

//加引用加快效率,不加引用的话把整个数组copy一遍,加了不用拷贝;数字存个位到0
vector<int> add(vector<int> &a,vector<int> &b){
    vector<int> c;
    //if(a.size()<b.size()) return add(b,a);
    int t=0;    //表示进位,一开始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;
    }
    //最后最高位还有没有进位,有的话加上1
    if(t) c.push_back(1);
    return c;
}
int main(){
    string str1,str2;
    vector<int> a,b;
    cin>>str1>>str2;
    //存储进去的是数字
    for(int i=str1.size()-1;i>=0;i--) a.push_back(str1[i]-'0');   //a={6,5,4,3,2,1}
    for(int i=str2.size()-1;i>=0;i--) b.push_back(str2[i]-'0');
    auto c=add(a,b);
    for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
    return 0;
}
3.4 高精度减法

题目:

题目:
现在有一个简单的问题,给你两个正整数A和B,你需要计算出A-B 的结果。不过要注意哦,这两个正整数非常大。
输入描述:
输入两个正整数A和 B ,A和 B的位数不超过 1000000。
输出描述:
输出 A-B,结果占一行。
数据范围:
1<=参数长度<=1e6;

题解:

    A3 A2 A1 A0
 -     B2 B1 B0
----------------
if A>=B
    A-B
else 
    -(B-A)
----------------
Ai-Bi-t   (1)当>=0  Ai-Bi-t
          (2)当<0   Ai-Bi+10-t
t为借为

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;

//判断a>=b
bool cmp(vector<int> &a,vector<int> &b){
    //位数不同的,位数大的数就大,位数小的数就小
    if(a.size()!=b.size()) return a.size()>b.size();
    //位数相同时,需要从大到小
    // for(int i=0;i<A.size();i++){
    //     if(a[i]!=b[i]) 
    //         return a[i]>b[i];
    // }
    for(int i=a.size()-1;i>=0;i--){
        if(a[i]!=b[i])
            return a[i]>b[i];
    }
    
    return true;
}
//  C=A-B
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;
        //判断Bi是否越界,否则借位
        if(i<b.size()) t-=b[i];
        /*
        (t+10)%10
        (1)t>=0 t本身
        (2)t<0  返回t+10
        */
        c.push_back((t+10)%10);

        //t<0表示Ai不够减需要借位t=1,不需要借位的话t=0
        if(t<0) t=1;
        else t=0;
    }
    //去掉前导0
    while(c.size()>1 && c.back()==0) c.pop_back();  
    return c;
}
int main(){
    string str1,str2;
    cin>>str1>>str2;
    vector<int> a,b;
    for(int i=str1.size()-1;i>=0;i--) a.push_back(str1[i]-'0');
    for(int i=str2.size()-1;i>=0;i--) b.push_back(str2[i]-'0');
    if(cmp(a,b)){
        auto c=sub(a,b);
        for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
    }
    else{
        auto c=sub(b,a);
        printf("-");
        for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
    }
    return 0;
}
3.5 高精度乘低精度

题目:

题目描述:
给定两个正整数A,B,请你计算A*B
输入格式:
共两行,第一行包含正整数A,第二行包含正整数B;
输出格式:
共一行,包含A*B的值;
数据范围:
1<=A长度<=1e6
1<=B<=1e4

题解:

       1  2  3     A
*         1  2     B
--------------
   C3 C2 C1 C0
    1  4  7  6
--------------
   C0=(3*12)%10=6
   T1=3*12/10=3
   C1=(2*12+T1)%10=7
   T2=2
   C2=(1*12+T2)%10=4
   T3=1
   C3=1
注意:将B看作一个整体

代码:

#include <bits/stdc++.h>
using namespace std;
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
        t/=10;          //进位t/10
    }
    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');
    auto c=mul(A,b);
    for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
    return 0;
}
3.6 高精度除以低精度

题目:

题目描述
现在给你一个简单的问题,给你两个正整数A和B,你需要计算出A除以B的结果。
不过要注意哦,这里的A非常大,b小。大到老师提醒你们需要使用高精度来做。
输入描述
输入两个正整数A和B,A的位数不超过10000,B的位数不超过100位。
输出描述
输出A除以B的结果,只保留整数部分,结果占一行。

题解:

为了统一数据存储,当运算时可能加减乘除都有,所以将除法也逆序存储

代码:

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

//A/b  商是c,余数是r
vector<int> div(vector<int> &A,int b,int &r){   //r是引用
    vector<int> c;  //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;
}
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');   //记得-'0'
    //r为余数
    int r;
    auto c=div(A,b,r);
    for(int i=c.size()-1;i>=0;i--) printf("%d",c[i]);
    cout<<endl<<r<<endl;
    return 0;
}

4.前缀和与差分

4.1 一维前缀和

题目:

题目描述:
有长度为n的整数序列,接下来再输入m次询问,每次询问输入一对l,r
对于每个询问,输出序列中从第1个数到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

题解:

a1 a2 a3 a4 a5 ...an
前缀和:Si=a1+a2+a3+..+ai
数组下标从1开始,Si要从1开始,主要是为了处理边界,比如求解{1,10}则S[10]-S[0]
1.S[i]怎么求   先定义:S[0]=0,后求S[i]=S[i-1]+a[i]
2.S[i]作用     求[l,r]和:S[r]-S[l-1]

S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
int a[N],s[N];
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
    while(m--){
        int l,r;
        cin>>l>>r;
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;
}
4.2 二维前缀和

题目:

题目描述
JM有一个一个n行m列的整数矩阵.
好奇的JM有q次询问,每个询问包含四个整数x_1, y_1, x_2, y_2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。

输入描述
第一行包含三个整数n,m,q。
接下来n行,每行包含m个整数a{ij},表示整数矩阵。
接下来q行,每行包含四个整数x_1, y_1, x_2, y_2 ,表示一组询问

题解:

S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n,m,q;
int a[N][N],s[N][N];
int main(){
    // ios::sync_with_stdio(false);
    scanf("%d%d%d",&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];
            scanf("%d",&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;
        scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
        //cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl; //算子矩阵
    }
    return 0;
}
4.3 一维差分

题目:

题目:
 输入一个长度为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

题解:

b[i]称为a[i]的差分,a[i]为b[i]的前缀和
    a[1] a[2] a[3] ..a[n]
构造b[1] b[2] b[3] ..b[n]使得a[i]=b[1]+b[2]+...+b[i]
    b[1]=a[1]
    b[2]=a[2]-a[1]
    b[3]=a[3]-a[2]
    .....
    b[n]=a[n]-a[n-1]

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

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int n,m;
int a[N],b[N];

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];
    for(int i=1;i<=n;i++) printf("%d ",b[i]);
    return 0;
}
4.4 二维差分

题目:

题目:
输入一个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
输入数据:
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

题解:

原矩阵a[i][j]

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

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,q;
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]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            insert(i,j,i,j,a[i][j]);
        }
    }
    while(q--){
        int x1,y1,x2,y2,c;
        cin>>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++)
            b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            printf("%d",b[i][j]);
        }
        puts("");
    } 
    return 0;
}

5.双指针算法

5.1 双指针算法

题目1:

题目:
输入字符串,将字符串以空格分开,每行一个字符

题解:

for(int i=0,j=0;i<n;i++){
    if(j<n && check(j)) j++;
    //题目逻辑思路
}
for(int i=0;i<n;i++)
    for(int j=0;j<m;j++)
        O(n^2)
运用双指针算法将朴素算法时间复杂度降为O(n)

代码:

#include <iostream>
#include <string.h>
using namespace std;
int main(){
    char str[100];
    gets(str);
    int len=strlen(str);
    for(int i=0;i<len;i++){
        int j=i;
        while(j<len && str[j] !=' ') j++;
        //这道题目的具体逻辑
        for(int k=i;k<j;k++) cout<<str[k];
        cout<<endl;
        //更新i
        i=j;
    }
    return 0;
}

题目2:

  题目:
  给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度
  输入格式:
  第一行包含整数n
  第二行包含n个整数,表示最长的不包含重复数字的连续子序列的长度
  数据范围:
  1<=n<=100000
  输入样例:
  5
  1 2 2 3 4
  输出样例:
  3

题解:

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;
    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
    
//朴素做法:O(n^2)
  for(int i-0;i<n;i++){
      for(int j=0;j<=i;j++){
          if(check(i,j)){
              res=max(res,i-j+1);
          }
      }
  }

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N],s[N];
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    int res=0;
    //j表示往左边离i最远到什么地方,j------i
    //当i向后面走的时候,j不可能向前面走
    for(int i=0,j=0;i<n;i++){
        s[a[i]]++;
        //新加入的数有重复的话s[a[i]]>1
        while(s[a[i]]>1){
            s[a[j]]--;
            j++;
        }
        res=max(res,i-j+1);
    }
    cout<<res<<endl;
    return 0;
}

6.位运算

6.1 位运算

题目1:

n的二进制表示中第k位是几

题解:

n=15=(1 1 1 1)2
      3 2 1 0
1.先把第k位移动到最后一位   n>>k
2.看个位(最后一位)是几     x&1

代码:

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n=10; //1010
    //倒着遍历
    for(int i=3;i>=0;i--) cout<<(n>>i & 1);
    return 0;
}

题目2:二进制中1的个数

题目:
给定一个长度为n的数列,求出数列中每个数的二进制表示中1的个数
输入:
第一行包含整数n
第二行包含n个整数,表示整数个数列
输出:
共一行,包含N个整数,其中第i个数表示数列中第i个数的二进制中1的个数
数据范围:
1<=n<=1e5
0<=元素值<=1e5
输入:
5
1 2 3 4 5
输出:
1 1 2 1 2

题解:

lowbit(x):返回x的最后(最右边)一个1
          x&-x=x&(~x+1) //求补码取反+1
          -x=~x+1  补码
          x=10101010 1 00000
         ~x=01010101 0 11111
       ~x+1=01010101 1 00000
   x&(~x+1)=00000000 1 00000
eg:
x=1010      lowbit(x)=10
x=101000    lowbit(x)=1000

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int lowbit(int x){
    return x&-x;
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    while(n--){
        int x;
        cin>>x;
        int res=0;
        while(x) x-=lowbit(x),res++;  //每次减去x的最后一位
        cout<<res<<" ";
    }   
    return 0;
}

7.整数离散化

7.1 离散化

题目:

假定有一个无限长的数轴,数轴上每个坐标上的数据都是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

题解:

离散化:
a[]  1   3   100   2000  50000
     0   1    2     3     4
     
(1)a[]中可能有重复元素-->去重
   unique去重:将数组中所有元素去重,并且返回去重之后元素的尾端点,在erase
(2)如何算出a[]离散化后的值-->二分
    找到第一个>=x的位置
(3)代码

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
}

代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N=3e5+10;
int n,m;
int a[N],s[N]; //s[]前缀和
vector<int> alls; //存的所有离散化得结果
typedef pair<int,int> PII;
vector<PII> add,query;  //插入操作,求操作
int find(int x){
    int l=0,r=alls.size()-1;
    while(l<r){
        int mid=r+l>>1;
        if(alls[mid]>=x) r=mid;
        else l=mid+1;
    }
    //映射为1,2,3,4...
    return r+1;
}
//自己定义unique 原理(1)它是第一个数 (2)a[i]!=a[i-1]
vector<int>::iterator unique(vector<int> &a){
    int j=0;  
    //遍历所有得数
    for(int i=0;i<a.size();i++)
        if(!i || a[i]!=a[i-1])
            a[j++]=a[i];
    //a[0]=a[j-1] 所有a中不重复的数
    return a.begin()+j;
}
int main(){
    ios::sync_with_stdio(false);
    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);  //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());

    alls.erase(unique(alls),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 区间合并

题目:

题目:
给定n个区间[l,r];要求合并所有有交集的区间
注意如果在端点处相交,也算有交集
输出合并完成后的区间个数
如:[1,3]和[2,6]可以合并为一个区间[1,6]
输入格式:
第一行包含整数n
接下来n行,每行包含两个整数l和r
输出格式:
共一行,包含一个整数,表示合并完成后的区间个数
输入:
5
1 2
2 4
5 6
7 8
7 9
输出:
3
数据范围:
1≤n≤100000,
−10^9≤l_i≤r_i≤10^9

题解:

1.按照区间左端点排序
2.扫描strt---end,将所有可能的区间合并

代码:

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10;
int n;
vector<PII> a;
void merge(vector<PII> &a){
    vector<PII> res;
    sort(a.begin(),a.end());
    int st=-2e9,ed=-2e9;     //设置边界值
    for(auto it:a){
        if(ed<it.first){
            if(st!=-2e9) res.push_back({st,ed});
            st=it.first,ed=it.second;
        }else{
            ed=max(ed,it.second);
        }
    }
    if(st!=-2e9) res.push_back({st,ed});  //防止输入是空
    a=res;
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r;
        cin>>l>>r;
        a.push_back({l,r});
    }
    merge(a);
    cout<<a.size()<<endl;
    return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值