算法思想:选择基准元素,比基准元素小的放左边,比基准元素大的放右边。每趟至少一个元素排好。
每一趟实现步骤:
参考:最详细的排序算法——快速排序 - 知乎
代码实现:
#include
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
给定一个长度为 n的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k个数。
分析:题目只要求找到第k个数,直接排序会用力过猛,时间复杂度更高。
算法思想:采用快排的思想,取基准元素x,进行一趟快速排序
代码实现:
#include
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> n >>k;
for(int i=0;i
算法分析:我们知道直接插入排序在元素有序时,时间复杂度可以达到O(N),归并排序则是借助于顺序,从1个元素开始归并,让每一段接近于有序,然后实行有序表的归并。
实现步骤:
代码实现:
#include
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++];
else
temp[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的整数数列,计算数列中的逆序对的数量。
算法思想:
1.采用分治的思想解决问题,对于每一次分段,逆序对有三种类型,即左侧逆序对的数量、右侧逆序对的数量、左右两侧逆序对的数量。左侧逆序对的数量和右侧逆序对的数量均可以通过分治转化为求左右两侧逆序对的数量
2. 如何求解左右两侧逆序对的数量呢?在进行归并合并时。如果a[i]>a[j],那么a[j]左侧同一段内的所有元素均能与a[i]组成一对逆序对。
代码实现:
#include
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的整数数组,以及 q个查询。对于每个查询,返回一个元素 k的起始位置和终止位置(位置从 0,0开始计数)。如果数组中不存在该元素,则返回 -1 -1。
算法分析:此处的二分是二分查找的拓展,要求能够找到与查询key相等的所有元素的起始位置和终止位置。
算法思想:
1.寻找左边界:
2.寻找右边界
代码实现:
#include
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]=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]=k
else 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,求它的三次方根。
浮点数二分的循环终止条件为high-low<1e-8,此时不用太过考虑终止条件。
#include
#include
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; //-11,立方根在区间[0,m]
if(m<1) r = 1; // 01e-8){
mid = (l+r)/2;
if(pow(mid,3)
高精度大整数之间的运算,此时由于数字较大,可能超出了计算机的表示范围,也可能出现溢出,使用数组存储每一个大整数。
与我们的加法计算相似,每一位的计算结果t为对应位的数字、低位进位之和。t%10为该位数字,t/10为进位。
#include
#include
using namespace std;
vector h_add(vector &A,vector &B){
vector C;
int t=0;
int n;
for(int i=0;i 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;
}
与我们的减法运算原理相同,每一位的减法结果为对应位数字减法之差,同时考虑低位借位。
#include
#include
using namespace std;
bool comp(vector &A,vector &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 h_sub(vector &A,vector &B){
vector C;
int t=0;
int n;
for(int i=0;i1 && C.back()==0) C.pop_back(); // 删除高位多余的0
return C;
}
int main(){
string a,b;
vector 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-B
auto C = h_sub(A,B);
for(int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
}
else{ // A=0;i--) printf("%d",C[i]);
}
return 0;
}
根据计算机组成原理我们知道,能够通过加法和移位实现乘法运算。 即:用一个数的每一位乘以另一个数的每一位,然后移位相加。
#include
#include
using namespace std;
vector h_mul(vector &A,vector &B){ //使用加法和移位实现高精度乘法
vector C(A.size()+B.size(),0); // 两个n位数相乘,最多2n位
int n,t;
for(int i =0;i1 && C.back()==0) C.pop_back();
return C;
}
int main(){
string a,b;
vector 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;
}
类似于我们计算除法的方式,从最高位开始,依次下移移位数字,求商和余数。
#include
#include
#include
using namespace std;
vector h_div(vector &A,int B, int &r){ // 一个高精度大整数除以一个小整数
vector 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(); // 去除高位多余的0
return C;
}
int main(){
string a;
int b;
vector 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;
}
差分与前缀和是一对逆运算,都是以空间换时间的思想。
前缀和矩阵即存储另一个矩阵的前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]的差分矩阵
前缀和即通过构建前缀和数组,从而实现在O(1)的时间复杂度内求出[L,R]区间的和,其结果为S[R]-S[L-1]
实例:输入一个长度为 n 的整数序列。接下来再输入 m个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l个数到第 r 个数的和。
#include
using namespace std;
const int N = 10010;
int main(){
int m,n; // m个询问,数组长度为n
int a[N],s[N]; // a[n]为原数组,s[n]为前缀和
s[0] = 0;
scanf("%d%d",&n,&m);
for(int i=0;i> l >> r; // 子区间的和
printf("%d\n",s[r]-s[l-1]);
}
return 0;
}
输入一个 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
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> 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;
}
差分的作用是将[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
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]);
}
}
二维矩阵在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
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--){ // 指定区间+c
cin >> 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");
}
}
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
算法思想:采用双指针策略i,j;i,j指向不重复子序列的开始和结束,辅助数据b[n]记录i,j区间内元素出现的次数。i前移,如果出现了重复元素,则j前移,直到区间内消除重复元素。
#include
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;i1){ // 此时,说明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] 固定a[j],让i后移,如果出现了a[i]+b[j]>x,则不可能找到与a[j]搭配的数,j前移 即: 给定一个长度为 n的整数序列 a1,a2,…,an以及一个长度为 m 的整数序列 b1,b2,…,bm。 请你判断 a序列是否为 b序列的子序列。 子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5}是列 {a1,a2,a3,a4,a5}的一个子序列。 算法思想:该题没有要求连续的公共子序列,因此,只需要从左向右遍历a数组,判断a数组的每一个元素是否在b数组中,且保持其相对顺序。 因此: 让我们回到计算机组成原理的求补电路,已知[x]补,[-x]补为将[x]补最低位的0和第一位1保留,其余各位包括符号位取反。即: [x]补=01111010 [-x]补=10000110 此时,若将x与-x,则结果为00000010,即得到x最低位的1。 应用: 给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 11 的个数。 算法思想:每次让x减去最低位的1,然后统计 示例:假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。接下来,进行 m次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r]之间的所有数的和。 算法思想:快速求区间和,很明显可以构建前缀和数组快速求解,但是在这个场景中,数组稀疏且无限长。因此,我们可以将区间离散化,只存储需要进行处理的位置,从而降低空间复杂度。具体包括以下步骤: 使用贪心策略进行区间合并,其步骤如下: 1.首先对区间左端点进行排序,即: 2.对于前后相邻的2个区间,有两种关系,即有交集和无交集。若无交集,则当前区间即为独立的区间。若有交集,则进行合并,end=max(end,next_end)。 代码如下: 参考:AcWing#include
#include
7.位运算
#include
8.区间和
#include
9.区间合并
#include