基础算法 - 常见算法模板题(最简洁写法)【上】

简介: 基础算法 - 常见算法模板题(最简洁写法)【上】

快速排序

思路:

  1. 确认分界点:x=q[(l+r)/2]
  2. 调整范围,使得在x左边的数小于x,右边的数大于x
  3. 递归处理左右两端
#include <iostream>
using namespace std;
const int N = 1000010;
int q[N];
void quick_sort(int q[],int l,int r)
{
    if(l>=r) return ;
    int i=l-1,j=r+1,x=q[l+r>>1];
    while(i<j)
    {
        do i++; while(q[i]<x);  //碰到大于x的停止
        do j--; while(q[j]>x);  //碰到小于x的停止
        if(i<j) swap(q[i],q[j]);
    }   //最终使得在x左边的数小于x,右边的数大于x
    quick_sort(q,l,j);  //对左区间进行处理
    quick_sort(q,j+1,r);
}
int main()
{
    int n;
    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;
}

第k个数

#include<iostream>
using namespace std;
int n,a[1000001],k;
void qsort(int a[],int l,int r)
{
    if(l>=r) return ;
  int i=l-1,j=r+1,x=a[l+r>>1];
  while(i<j)
  {
      do i++; while(a[i]<x);
      do j--; while(a[j]>x);
      if(i<j) swap(a[i],a[j]);
  }
  qsort(a,l,j);
  qsort(a,j+1,r);
}
int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++) cin>>a[i];
    qsort(a,0,n-1);
    cout<<a[k-1]<<" ";
}

归并排序

思路:

  1. 递归均分如图区间
  2. 对每层区间数值按照从小到大顺序存入tmp(可能是奇数无法均分,需要将剩余直接着存入)
  3. 将该层tmp中排好序的数值放入原来的q中
  4. 回溯到上一层,重复2,3操作,直到最顶层的left>=right,排序完成
#include<iostream>
using namespace std;
const int N = 1000010;
int n;
int q[N], tmp[N];
void merge_sort(int q[], int l, int r)
{
  if (l >= r) return;
  int mid = l + r >> 1;
  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++];//按照从小到大顺序存入tmp
    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];//把排好的数放回q
}
int main()
{
  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;
}

逆序对的数量

- 归并排序合并过程中计算逆序对数量

- 若 a[i] > a[j],则a[i] 和它后面的元素都大于 a[j],a[i] 构成逆序对数量:res += mid - i + 1;


例如上图合并的一个过程,由4 5 7 8   1 2 3 6 合并为 1 2 3 4 5 6 7 8,对于4有 4>1 所以逆序对数res 就等于4后面的所有数字再加自己本身,即res==(mid-i) +1;


观察整个合并图发现,对每个小的数组合并成大数组累加逆序对,最终不重不漏得到所有逆序对

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], tmp[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
        {
            res+=mid-i+1;
            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];
    return res;
}
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    cout << merge_sort(a, 0, n - 1) << endl;
    return 0;
}

二分查找

二分模板

//区间[l,r]被划分成[l,mid]和[mid + 1,r]时使用:
int bsearch_1(int 1, int r)
{
  while (l < r) 
  {
    int mid = l + r >> 1;
    if (check(mid)) r = mid;  //判断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;
}

记忆口诀:有减必有加

数的范围

思路:

  1. 利用二分,保证每次缩小范围时所求值都在范围中
  2. 一个需要找到>=x的第一个数,另一个需要找到<=x的最后一个数
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N];
int SL(int l, int r, int x) {
  while (l < r) {
    int mid = l + r >> 1;
    if (q[mid] >= x) r = mid;
    else l = mid + 1 ;
  }
  return l;//最终找到>=x的第一个数
}
int SR (int l, int r, int x) {
  while (l < r) {
    int mid = l + r + 1 >> 1;
    if(q[mid] <= x) l = mid;
    else r = mid - 1;
  }
  return r;//最终找到<=x的第一个数
}
int main() { int n,m;
    scanf ("%d%d",&n,&m);
    for(int i=0;i<n;++i) scanf ("%d",&q[i]);
    while ( m-- ) {
        int x;
        scanf ("%d",&x);
        int l = SL(0, n - 1, x);//查找左边界 并返回下标l
        if (q[l]!=x) cout <<"-1 -1"<<endl;//如果找不到  返回-1 -1
        else {
            cout << l << ' '; //如果找到了  输出左下标
            cout << SR(0, n - 1, x) << endl; //输出右下标
        }
    }
    return 0;
}

浮点数二分

思路:

因为完全不必考虑 m=(l+r)/2;导致精度丢失的问题,所以可以直接缩范围

(借用大佬笔记)

#include<iostream>
using namespace std;
int main()
{
    double l=-100000,r=100000;    //数据结果必在其之间,不用思考
    double n,m;
    cin>>n;
    while(r-l>1e-8)    //精确到为1e-6,所以至少要多精确两位
    {
        m=(l+r)/2;
        if(m*m*m>=n) r=m;    //立方根n在mid的左边,缩右边界
        else l=m;
    }
    printf("%.6f",m);
   return 0;
}

高精度

高精度加法

思路:

#include <iostream>
using namespace std;
const int N = 100010;
int A[N], B[N], C[N];
int Add(int a[], int b[], int c[], int cnt) {
    int t = 0;//t表示进位
    for (int i=1; i<=cnt; i++) {
        t += a[i] + b[i];//进位加上a和b第i位上的数
        c[i] = t % 10;//c的值就是进位的个位数
        t /= 10;//把t的个位数去掉只剩下十位数,即只剩下这个位置的进位
    }
    if (t) c[++cnt] = 1;//如果t==1,表示还有一个进位,要补上
    return cnt;
}
int main() {
    string a, b;
    cin >> a >> b;  
    //A和B倒着放进int数组,因为有进位,倒着放容易处理
    int cnt1 = 0;
    for (int i=a.size()-1; i>=0; i--)
        A[++cnt1] = a[i] - '0';
    int cnt2 = 0;
    for (int i=b.size()-1; i>=0; i--)
        B[++cnt2] = b[i] - '0';
    int tot = Add(A, B, C, max(cnt1, cnt2));
    //因为A和B是倒着放的,所以C也要倒着输出
    for (int i=tot; i>=1; i--)
        cout << C[i];
}

高精度减法

模拟手算减法

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
 string 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;    //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;   //借位
        if(i<B.size()) t-=B[i]; //两数相减
        C.push_back((t+10)%10); //如果t>0,入t;如果t<0,入(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()
{
    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;
    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];
    cout<<endl;
    return 0;
}

高精度乘法(高精度x低精度)

#include<iostream>
#include<vector>
using namespace std;
vector<int> mul(vector<int>& A,int b)
{
    vector<int> C;
    int t=0;
    for(int i=0;i<A.size();i++)
    {
        t+=A[i]*b;
        C.push_back(t%10);
        t/=10;
    }
    while(t)
    {
        C.push_back(t%10);
        t/=10;
    }
    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');
    vector<int> C=mul(A,b);
    for(int i=C.size()-1;i>=0;i--) cout<<C[i];
    return 0;
}

高精度除法

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> div(vector<int>&A,int B,int &r)
{
    vector<int> C;
    for(int i=0;i<A.size();i++)//模拟手算除法
    {
        r=r*10+A[i];
        C.push_back(r/B);
        r%=B;
    }
    reverse(C.begin(),C.end());//去除前导0
    while(C.size()>1&&C.back()==0)
        C.pop_back();
    return C;
}
int main()
{
    string a;
    int B,r=0;
    cin>>a>>B;
    vector<int> A;
    for(int i=0;i<a.size();i++)
        A.push_back(a[i]-'0');
    vector<int> C=div(A,B,r);
    for(int i=C.size()-1;i>=0;i--) cout<<C[i];//返回为反转的,反转输出调正
    cout<<endl<<r;
    return 0;
}

前缀和与差分

前缀和

思路:

给出ai的值时算出前n项和的值

只需要 s[r]-s[l-1] 即可求出 l 到 r 之间的值


技巧:

ios::sync_with_stdio(false);

作用:提高cin和cout的速度

副作用:不能使用scanf和printf

#include<iostream>
using namespace std;
const int N=1e6+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];
        s[i]=s[i-1]+a[i];
    }
    while(m--)
    {
        int l,r;
        cin>>l>>r;
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;
}

子矩阵的和

思路:

s[i][j]为从(1,1)到(i,j)所有整数的和

S[i,j]即为所有数的的和为:S[i,j]=S[i,j−1]+S[i−1,j]−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]

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N], s[N][N];
int main()
{
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin>>a[i][j];
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    while(q--)
    {
        int x1,x2,y1,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;
}

差分

 

思路:


首先给定一个原数组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数组的b[i]的修改,会影响到a数组中从a[i]及往后的每一个数。

首先让差分b数组中的 b[l] + c ,a数组变成 a[l] + c ,a[l+1] + c,,,,,, a[n] + c;

然后我们打个补丁,b[r+1] - c, a数组变成 a[r+1] - c,a[r+2] - c,,,,,,,a[n] - c;

核心操作:对差分数组b做 b[l] + = c, b[r+1] - = c(时间复杂度为O(1) )

a[i]为b[i]前缀和的实现

令a[i]=a[i-1]+b[i]; ---> b[i]=a[i]-a[i-1];

即:

a[0 ]= 0;

b[1] = a[1] - a[0];

b[2] = a[2] - a[1];

b[3] =a [3] - a[2];

........

b[n] = a[n] - a[n-1];

总结a[i]为b[i]的前缀和 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i]

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[i] = a[i] - a[i - 1];      //由前缀和公式,构建差分数组
    }
    int l, r, c;
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c;     //将序列中[l, r]之间的每个数都加上c
        b[r + 1] -= c;
    }
    for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i - 1];    //更新a[i],前缀和运算
        printf("%d ", a[i]);
    }
    return 0;
}

差分矩阵

思路:

a[][]数组是b[][]数组的前缀和数组(b[i][j]改变影响a[i][j]之后的所以值),那么b[][]a[][]的差分数组,我们去构造差分数组: b[i][j],使得a数组中a[i][j]b数组左上角(1,1)到右下角(i,j)所包围矩形元素的和


先看核心操作:在范围(x1,y1)到(x2,y2)的方形数组中加c

b[x1][y1] + = c;

b[x1,][y2+1] - = c;

b[x2+1][y1] - = c;

b[x2+1][y2+1] + = c;

(假设有b差分数组)

完成上述操作需要构造差分数组b:

  void insert(int x1,int y1,int x2,int y2,int c)
  {     //对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
      b[x1][y1]+=c;
      b[x2+1][y1]-=c;
      b[x1][y2+1]-=c;
      b[x2+1][y2+1]+=c;
  }
  for(int i=1;i<=n;i++)
  {
      for(int j=1;j<=m;j++)
      {
          insert(i,j,i,j,a[i][j]);    //构建差分数组
      }
  }

差分这样构建的原因:首先假设a,b都为空数组,也就是全部为0的情况,那么我们也可以说,b数组是a数组的差分数组!因为a[i][j] = b[1][1]+…b[i][j],因为都是0,所有情况满足!在这时a数组其实不为空,我们把a数组中的值看作(0+a[i][j])那么a数组的值变了,变成了a[i][j],这时如果b数组要想成为a数组的差分数组,值也要变为a[i][j],所以我们把b[i][j]变为a[i][j],这样就能满足b数组是a数组的差分数组。也就相当于想b数组中插入a[i][j].

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e3 + 10;
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()
{
    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++)
        {
            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];  //二维前缀和
            //从(1,1)到(i,j)的整数和b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1]+b[i][j]
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}
相关文章
|
6月前
|
存储 算法 数据管理
【C/C++ 基础算法】 C/C++ 位图算法的使用
【C/C++ 基础算法】 C/C++ 位图算法的使用
113 0
|
6月前
|
算法
Hierholzer算法dfs找欧拉回路模板
Hierholzer算法dfs找欧拉回路模板
69 0
|
人工智能 算法
【AcWing算法基础课】第一章 基础算法(部分待更)(3)
输入n个字符串(不含空格),由空格隔开。每行依次输出每个字符串。
76 0
|
3月前
|
算法
【算法】前缀和——二维前缀和模板题
【算法】前缀和——二维前缀和模板题
|
5月前
|
算法 Java 数据处理
Java算法模板 数据流快读
Java算法模板 数据流快读
36 2
|
4月前
|
设计模式 JavaScript 算法
vue2 原理【详解】MVVM、响应式、模板编译、虚拟节点 vDom、diff 算法
vue2 原理【详解】MVVM、响应式、模板编译、虚拟节点 vDom、diff 算法
164 0
|
5月前
|
算法 前端开发 安全
C++算法模板
C++算法模板
32 0
|
6月前
|
算法
简记二分算法模板与代码案例:整数二分和浮点数二分
本文介绍了两种算法模板,分别是整数二分和浮点数二分。
50 0
|
6月前
|
存储 机器学习/深度学习 人工智能
【算法基础】基础算法(三)--(双指针算法、位运算、离散化、区间合并)
【算法基础】基础算法(三)--(双指针算法、位运算、离散化、区间合并)
|
6月前
|
人工智能 算法 BI
【算法基础】基础算法(二)--(高精度、前缀和、差分)(下)
【算法基础】基础算法(二)--(高精度、前缀和、差分)(下)