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++];
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;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]=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;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(); // 删除高位多余的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(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<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(); // 去除高位多余的0
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');
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个询问,数组长度为n
int 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--){ // 指定区间+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");
}
}
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,r
vector<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