基础算法
1.快速排序
分治
1.确定分界点x,左边,中间,右边
2.i ,j q[i]<x q[j]>x
3.分治递归
#include<iostream>
using namespace std;
const int N=1e6+10;
int q[N];
int n;
void quick_sort(int q[],int l,int r){
if (l>=r) return;
int x=q[(l+r)>>1],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);
}
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;
}
- Acwing 786 第k个数
2.归并排序
- AcWing 787. 归并排序
1.确定分界点,mid=l+r>>1
2.左边右边分治
3.双指针,两个数组按顺序排序
#include<iostream>
#include<cstring>
#include<algorithm>
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++];
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] ;
}
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;
}
- AcWing 788. 逆序对的数量
3.二分查找
- AcWing 789. 数的范围
/*
方法1:手写二分,注意分界点的情况,避免陷入死循环。
*/
#include<iostream>
using namespace std;
const int maxn=100010;
int n,q,k,a[maxn];
int main(){
scanf("%d%d",&n,&q);
for (int i=0;i<n;i++) scanf("%d",&a[i]);
while(q--){
scanf("%d",&k);
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if (a[mid]<k) l=mid+1;
else r=mid;
}
if (a[l]!=k){
printf("-1 -1\n");
continue;
}
int l1=l,r1=n;
while(l1+1<r1){
int mid=l1+r1>>1;
if(a[mid]<=k) l1=mid;
else r1=mid;
}
printf("%d %d\n",l,l1);
}
return 0;
}
/*
方法2:直接调用函数
binary_search(a,a+n,k)
lower_bound(a,a+n,k)-a
upper_bound(a,a+n,k)-a-1
*/
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,q,k,a[maxn];
int main(){
scanf("%d%d",&n,&q);
for (int i=0;i<n;i++) scanf("%d",&a[i]);
while(q--){
scanf("%d",&k);
if(binary_search(a,a+n,k)){
cout<<lower_bound(a,a+n,k)-a<<" "<<upper_bound(a,a+n,k)-a-1<<endl;
}
else{
cout<<"-1 -1"<<endl;
}
}
return 0;
}
4.高精度
- AcWing 791. 高精度加法
5.前缀和与差分
-
- AcWing. 前缀和
输入一个长度为 n的整数序列。
接下来再输入 m个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l个数到第 r 个数的和。
#include<iostream>
using namespace std;
const int maxn=100010;
int n,m,l,r,a[maxn],s[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for (int i=1;i<=n;i++) {
cin>>a[i];
s[i]=s[i-1]+a[i];
}
while(m--){
cin>>l>>r;
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
- AcWing 796. 子矩阵的和
#include<iostream>
using namespace std;
const int maxn = 1001; // 定义最大数组大小,确保足够存储数据,最大为 1000x1000 的矩阵
int n, m, q; // n: 行数, m: 列数, q: 查询次数
int x1, y1, x2, y2; // 查询矩形区域的两个对角坐标
int a[maxn][maxn], s[maxn][maxn]; // a: 原始矩阵, s: 前缀和矩阵
int main(){
ios::sync_with_stdio(false); // 禁用同步加速输入输出
cin.tie(0); // 解除 cin 和 cout 的绑定,进一步提高输入输出速度
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] 表示从 (1,1) 到 (i,j) 矩形区域的元素和
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
}
// 处理 q 次查询
while(q--){
cin >> x1 >> y1 >> x2 >> y2; // 输入矩形区域的左上角 (x1, y1) 和右下角 (x2, y2)
// 使用二维前缀和公式计算 (x1, y1) 到 (x2, y2) 的矩形区域的元素和:
// s[x2][y2] 表示从 (1,1) 到 (x2, y2) 的区域和
// 减去多余的区域:s[x2][y1-1] 和 s[x1-1][y2]
// 加回因为重叠部分被减了两次的 s[x1-1][y1-1]
cout << s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1] << endl;
}
return 0;
}
- AcWing 797. 差分
差分数组:
首先给定一个原数组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数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。
#include<iostream>
using namespace std;
const int maxn=100010;
int l,r,c;
int n,m;
int a[maxn],b[maxn],s[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i]-a[i-1];
}
while(m--){
cin>>l>>r>>c;
b[l]+=c;
b[r+1]-=c;
}
for(int i=1;i<=n;i++){
s[i]=s[i-1]+b[i];
cout<<s[i]<<" ";
}
return 0;
}
- AcWing 798. 差分矩阵
#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]; //二维前缀和
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
printf("%d ", b[i][j]);
}
printf("\n");
}
return 0;
}
6.双指针算法
- AcWing 799. 最长连续不重复子序列
维护一个双指针j,i。假设j到i-1是最长连续不重复子序列,那么j到i不是的话一定是a[i]重复了,将j的位置进行调整,向右移动找到重复的a[i]
#include<iostream>
using namespace std;
const int maxn=100001;
int n,r;
int a[maxn],s[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
int j=1;
for(int i=1;i<=n;i++){
cin>>a[i];
++s[a[i]];
while(s[a[i]]>1) --s[a[j++]];
r=max(r,i-j+1);
}
cout<<r;
return 0;
}
- AcWing 800. 数组元素的目标和
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100001;
int n,m,x;
int a[maxn],b[maxn],flag[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>x;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
for(int i=0;i<=n;i++){
if (binary_search(b,b+m,x-a[i])){
cout<<i<<" "<<upper_bound(b,b+m,x-a[i])-b-1<<endl;
break;
}
}
return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100001;
int n,m,x;
int a[maxn],b[maxn],flag[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>x;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
for(int i=0;i<=n;i++){
int y=x-a[i];
int l=0,r=m-1;
while(l<r){
int mid=l+r>>1;
if(b[mid]<y) l=mid+1;
else r=mid;
}
if(b[l]==y){
cout<<i<<" "<<l<<endl;
break;
}
}
return 0;
return 0;
}
- AcWing 2816. 判断子序列
遍历数组看看有没有子序列。如果j<m则说明没有包含。
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100001;
int a[maxn],b[maxn];
int n,m;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
int j=0;
for (int i=0;i<m;i++){
if(b[i]==a[j]) j++;
}
// cout<<j<<endl;
if(j>=n){
cout<<"Yes";
}else{
cout<<"No";
}
return 0;
}
7.位运算
lowbit原理
根据计算机负数表示的特点,如一个数字原码是10001000,他的负数表示形势是补码,就是反码+1,反码是01110111,加一则是01111000,二者按位与得到了1000,就是我们想要的lowbit操作。
- AcWing 801. 二进制中1的个数
(lowbit) O(nlogn)
使用lowbit操作,进行,每次lowbit操作截取一个数字最后一个1后面的所有位,每次减去lowbit得到的数字,直到数字减到0,就得到了最终1的个数,
#include<iostream>
using namespace std;
const int maxn=10010;
int n;
int lowbit(int x){
return x&(-x);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
while(n--){
int x,cnt=0;
cin>>x;
while(x){
x-=lowbit(x);
cnt+=1;
}
cout<<cnt<<" ";
}
return 0;
}
8.离散化
- AcWing 802. 区间和
由于题目区间范围为−109≤x≤109,比较大,但是数值只有1e5,所以可以将区间离散化。
可以将所有的下标都存到vector里面,然后排序去重,就相相当于按顺序离散化了
然后通过二分查找找到真实的数在vertor里的位置,就当作离散化后位置。
然后前缀和。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=300010;
int n,m;
int a[maxn]; //离散化之后存储插入的值
int s[maxn]; //存储数组a的前缀和
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=l+r>>1;
if(alls[mid]<x) l=mid+1;
else r=mid;
}
return r+1;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m ;
for(int i=1;i<=n;i++){
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x) ;
}
for(int i=1;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());
//执行前n次插入
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];
//处理后m次查询操作
for(auto item:query){
int l=find(item.first);
int r=find(item.second);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
9.区间合并
- AcWing 803. 区间合并
- 按照区间第一个点来排序
- 然后来维护区间,判断当前区间与后面区间的情况,一共有三种
- 如果两个区间无法合并,则区间数量加1;
- 如果两个区间可以合并,并且互不包含,则区间合并
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
typedef pair<int,int> PII;
vector<PII> nums,res;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int st=-2e9,ed=-2e9; //区间结尾和开头;
int n;
cin>>n;
while(n--){
int l,r;
cin>>l>>r;
nums.push_back({l,r});
}
sort(nums.begin(),nums.end());
for (auto num:nums){
//情况1,两个区间无法合并
if(ed<num.first){
if(ed!=-2e9) res.push_back({st,ed});
st=num.first,ed=num.second;
}
//情况2,两个区间可以合并,且区间11不包含区间2,区间2不包含区间1;
else if (ed<num.second) ed=num.second; //区间合并;
}
res.push_back({st,ed});
cout<<res.size();
return 0;
}