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

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++];elsetemp[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]=kelse 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();  // 删除高位多余的0return 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-Bauto 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(); // 去除高位多余的0return 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个询问,数组长度为nint 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--){   // 指定区间+ccin >> 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,rvector<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

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

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

相关文章

ZOJ 3537 Cake 【区间DP + 凸多边形三角剖分】

Cake 题意 给定平面坐标上的 n n n 个点&#xff0c;如果是凸多边形的话&#xff0c;就用最少的花费把这个多边形剖分成若干个三角形&#xff0c;剖分的线段端点只能是原多边形的顶点&#xff0c;一条线段的花费为&#xff1a; ∣ x i x j ∣ ∣ y i y j ∣ m o d p |x_i…

部署一个在线OCR工具

效果 安装 1.拉取镜像 # 从 dockerhub pull docker pull mmmz/trwebocr:latest 2.运行容器 # 运行镜像 docker run -itd --rm -p 10058:8089 --name trwebocr mmmz/trwebocr:latest 使用 打开浏览器输入 http://192.168.168.110:10058/ 愉快滴使用吧

亚马逊认证考试系列 - 知识点 - LightSail介绍

一、引言 在当今云计算的时代&#xff0c;亚马逊网络服务&#xff08;AWS&#xff09;已成为业界领先的云服务提供商。其中&#xff0c;LightSail服务是AWS为简化云计算的入门和使用而推出的一项服务。它特别适合那些想要快速搭建网站、开发环境或小型应用的用户。通过LightSa…

Android Graphics 图像显示系统 - 开篇

“ 随着学习的不断深入和工作经验的积累&#xff0c;欲将之前在博客中整理的Android Graphics知识做进一步整理&#xff0c;并纠正一些理解上的错误&#xff0c;故开设Graphics主题系列文章 ” 序言 由于工作需要&#xff0c;也源于个人兴趣&#xff0c;终于下决心花时间整理一…

网络请求库axios

一、认识Axios库 为什么选择axios? 功能特点: 在浏览器中发送 XMLHttpRequests 请求在 node.js 中发送 http请求支持 Promise API拦截请求和响应转换请求和响应数据 补充: axios名称的由来? 个人理解没有具体的翻译. axios: ajax i/o system 二、axios发送请求 1.axios请求…

【开源】SpringBoot框架开发大病保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统配置维护2.2 系统参保管理2.3 大病保险管理2.4 大病登记管理2.5 保险审核管理 三、系统详细设计3.1 系统整体配置功能设计3.2 大病人员模块设计3.3 大病保险模块设计3.4 大病登记模块设计3.5 保险审核模块设计 四、…

2024 年 5 款适用于免费 iPhone 数据恢复的工具软件

搜索一下&#xff0c;你会发现许多付费或免费的iPhone数据恢复工具声称它们可以帮助你以很高的成功率找回所有丢失的数据。然而&#xff0c;这正是问题所在。真的很难做出选择。为了进一步帮助您解决数据丢失问题&#xff0c;我们在此列出了 5 款最好的免费 iPhone 恢复软件供您…

滑块识别验证

滑块识别 1. 获取图片 测试网站&#xff1a;https://www.geetest.com/adaptive-captcha-demo 2. 点击滑块拼图并开始验证 # 1.打开首页 driver.get(https://www.geetest.com/adaptive-captcha-demo)# 2.点击【滑动拼图验证】 tag WebDriverWait(driver, 30, 0.5).until(la…

Python API的使用简述

文章目录 Web APIGit 和 GitHub使用 API 调用请求数据安装 requests处理响应 API处理响应字典监视API的速率限制使用 Pygal 可视化仓库改进Pygal图表添加自定义工具提示 本篇文章&#xff1a;我们叙述如何编写一个独立的程序&#xff0c;并对其获取的数据进行可视化。这个程序将…

Eclipse导入maven项目或者创建maven项目时,报错Could not calculate build plan: Plugin

问题&#xff1a;Eclipse导入maven项目或者创建maven项目时,报错Could not calculate build plan: Plugin 1.上述问题大概是项目不能加载此maven插件&#xff0c;在pom文件中添加依赖项 <dependency><groupId>org.apache.maven.plugins</groupId><artifa…

二级建造师试题答案?学生党都在用的6款搜题工具来了 #学习方法#学习方法#微信

作为大学生&#xff0c;我们应该善于利用各种学习工具&#xff0c;提高学习效率和质量。 1.灵兔搜题 这是一个公众号 包含大学网课、课后教材、选修课、mooc慕课及各类职业资格证、学历提升考试、公务员考试等常见题库。 下方附上一些测试的试题及答案 1、Uri主要由三部分组…

Python实战:用Python程序实现春晚刘谦魔术

刘谦春晚魔术是一个让人叹为观止的魔术表演&#xff0c;其中涉及到了数学、编程和创意的结合。看了春晚魔术的朋友们&#xff0c;是不是好奇春晚刘谦的魔术是怎么变的。 在这篇文章中&#xff0c;我们将通过 Python 程序实现春晚刘谦魔术&#xff0c;让读者对这个魔术有更深入…

【GAMES101】Lecture 20 光场

目录 光场&#xff08;Light Field / Lumigraph&#xff09; 广场照相机 光场&#xff08;Light Field / Lumigraph&#xff09; 我们在三维的世界中从一个观测点出发看到这么一幅二维的画面 如果有这么一副画布可以完美的显示出从观察点看到的画面&#xff0c;那用这幅画布…

idea设置terminal为git

要在IntelliJ IDEA中设置终端为Git Bash&#xff0c;请按照以下步骤操作&#xff1a; 打开 Settings&#xff08;设置&#xff09;。点击 Tools&#xff08;工具&#xff09;选项卡。进入 Terminal&#xff08;终端&#xff09;界面。在 Shell Path 下选择 Browse&#xff08;…

Redis发布订阅及事务管理

目录 一、发布订阅 1.1、常用命令 1.2、示例演示 二、事务管理 2.1 Multi、Exec、Discard 2.2 示例演示 2.3 事务的错误处理 2.4 事务的冲突问题 2.4.1 事务场景 2.4.2 悲观锁 2.4.3 乐观锁 2.4.4 事务解决冲突—WATCH 2.4.5 UNWATCH 2.4.6 Redis事务的三个特性 …

ES实战-book笔记1

#索引一个文档,-XPUT手动创建索引, curl -XPUT localhost:9200/get-together/_doc/1?pretty -H Content-Type: application/json -d {"name": "Elasticsearch Denver","organizer": "Lee" } #返回结果 {"_index" : "g…

尚硅谷 Vue3+TypeScript 学习笔记(中)

目录 三、路由 3.1. 【对路由的理解】 3.2. 【基本切换效果】 3.3. 【两个注意点】 3.4.【路由器工作模式】 3.5. 【to的两种写法】 3.6. 【命名路由】 3.7. 【嵌套路由】 3.8. 【路由传参】 query参数 params参数 3.9. 【路由的props配置】 3.10. 【 replace属性…

【GAMES101】Lecture 20 颜色

目录 光 颜色 加色系统 CIE RGB颜色匹配实验 颜色空间 CIE XYZ颜色空间 HSV颜色空间(Hue-Saturation-Value) CIELAB空间 减色系统&#xff1a;CMYK 光 光是由不同波长的光波组成的&#xff0c;其中可见光的波长范围在400nm到700nm 用谱功率密度&#xff08;Spectral…

Low 级别反射型 XSS 攻击演示(附链接)

环境准备 如何搭建 DVWA 靶场保姆级教程&#xff08;附链接&#xff09;https://eclecticism.blog.csdn.net/article/details/135834194?spm1001.2014.3001.5502 测试 打开 DVWA 靶场并登录&#xff0c;找到反射型 XSS 页面&#xff08;笔者这里是 Low 级别&#xff09; 先…

图像处理之《鲁棒图像隐写术:隐藏频率系数中的信息》论文精读

一、文章摘要 隐写术是一种将秘密信息隐藏到公共多媒体对象中而不会引起第三方怀疑的技术。然而&#xff0c;大多数现有的工作不能提供良好的抗有损JPEG压缩鲁棒性&#xff0c;同时保持相对较大的嵌入容量。提出了一种基于可逆神经网络的端到端鲁棒隐写系统。该方法将秘密信息…