一.快速排序
1.快速排序(模版)
#include<iostream>
using namespace std;
int a[100010];
int n;
void quickSort(int a[], int l, int r){
//如果数组中就一个数,就已经排好了,直接返回。
if(l >= r) return;
//选取分界线。这里选数组中间那个数
int x = a[l + r >> 1];
int i = l - 1, j = r + 1;
//划分成左右两个部分
while(i < j){
while(a[++i] < x);
while(a[--j] > x);
if(i < j){
swap(a[i], a[j]);
}
}
//对左部分排序
quickSort(a, l, j);
//对右部分排序
quickSort(a, j + 1, r);
}
int main(){
cin >> n;
for(int i = 0; i < n; i++){
cin >> a[i];
}
quickSort(a, 0, n - 1);
for(int i = 0; i < n; i++){
cout << a[i] << " ";
}
return 0;
}
异常:
错误代码:
//初始化i,j
int i=l,j=r;
while(a[i]<x)i++;
while(a[j]>x)j--;
这样初始化会死循环, 如3 2 2
2.第k个数
跟上题一样,只要输出第k个数就好了
#include<iostream>
using namespace std;
int a[100010];
int n,k;
void quickSort(int a[],int l, int r){
//如果数组中就一个数,就已经排好了,直接返回。
if(l >= r) return;
//选取分界线。这里选数组中间那个数
int x = a[l + r >> 1];
int i = l-1 , j = r+1 ;
//划分成左右两个部分
while(i < j){
while(a[++i] < x);
while(a[--j] > x);
if(i < j){
swap(a[i], a[j]);
}
//cout<<a[0]<<' '<<a[1]<<' '<<a[2];
}
//对左部分排序
quickSort(a, l, j);
//对右部分排序
quickSort(a, j + 1, r);
}
int main(){
cin >> n>>k;
for(int i = 0; i < n; i++){
cin >> a[i];
}
quickSort(a,0, n - 1);
cout<<a[k-1];
return 0;
}
二. 归并排序
3.归并排序(模版题)
#include<iostream>
using namespace std;
int q[100010],tmp[100010];
int 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()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>q[i];
merge_sort(q,1,n);
for(int i=1;i<=n;i++)cout<<q[i]<<' ';
}
4.逆序对的数量
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int temp[N];
long long find(int a[], int l, int r){
if(l >= r) return 0;
int mid = l + r + 1 >> 1);
long long res = 0;
res += find(a, l, mid);//左边逆序对的数量
res += find(a, mid + 1, r);//右边逆序对的数量
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r){
if(a[i] <= a[j]) temp[k++] = a[i++];
else//a[i]>a[j] 逆序对
{
temp[k++] = a[j++];
res += mid - i + 1;//i~mid之间的数对a[j]都构成逆序对
}
}
while(i <= mid) temp[k++] = a[i++];
while(j <= r) temp[k++] = a[j++];
for(i = l,k = 0;i <= r;i++)
a[i] = temp[k++];
return res;
}
int main(){
int n;
cin >> n;
for(int i = 0; i < n;i++){
cin >> a[i];
}
cout << find(a, 0 ,n - 1);
}
三. 二分
模版(向左搜索)
int l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(check())r=mid;
else l=mid+1;
}
模版(向右搜索)
int l=0,r=n-1;
while(l<r)
{
int mid=l+r+1;//必须加1,否则会死循环
if(check())l=mid;
else r=mid-1;
}
前置知识:lower_bound和upper_bound的应用(头文件:algorithm)
lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。
在从小到大的排序数组中,
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
int a[5] = {5, 3, 2, 1, 9};
sort(a, a + 5);//首先需要排序
//因为lower_bound返回的是地址,所以需要先减掉a的首地址
int n1 = lower_bound(a, a + 5, 3) - a;//找到第一个大于或等于3得数的下标
int n2 = upper_bound(a, a + 5, 3) - a;//找到第一个大于3的数的下标
cout << n1 << '\n'
<< n2;
}
5.数的范围
STL版本
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 100005;
int n,q,k,x1,x2,t;
int a[N];
int main() {
cin >> n >> q;
for (int i = 0; i < n; i++)
cin >> a[i];
while (q--) {
cin >> k;
//t = *find(a, a + n, k);
x1 = lower_bound(a, a + n, k) - a;
x2 = upper_bound(a, a + n, k) - a-1;
if(x1 == x2 + 1)
cout << -1 << ' ' << -1 << endl;
else
cout << x1 << ' '<< x2 << endl;
}
return 0;
}
6.数的三次方根
题目要求结果保存6位小数,保险起见我们开到-8次方
#include <iostream>
using namespace std;
double n;
int main()
{
cin >> n;
double l = -10000, r = 10000;
while (r - l > 1e-8)//相当于l和r至少都是8位小数,两者之差也是8位小数
{
double mid = (l + r) / 2;
if (mid * mid * mid >= n)
r = mid;
else
l = mid;
}
printf("%.6lf", l);
return 0;
}
四.高精度
高精度是为了解决数溢出的算法,运算的结果可能会有几十位,几十位的数如何加减乘除
整体思路:
首先是string字符串读入,string的极限是1024位字符,但真正的读入是1023位,最后一位读入'\n'换行符。然后传入vector结构,最后运算,详细看下列算法。
7.高精度加法
加法是从低位向高位算的,所以字符串倒着存
#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+10;
vector<int>add(vector<int >&A,vector<int>&B)
{
vector<int >C;
int t=0;
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];
C.push_back(t%10);
t/=10;
}
if(t)C.push_back(1);
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=add(A,B);
for(int i=C.size()-1;i>=0;i--)cout<<C[i];
return 0;
}
8.高进度减法
减法需要多考虑两个方面,一是要去掉多余的前导0,二是可能会出现负数,要特判一下
#include<iostream>
#include<vector>
using namespace std;
string a,b;
vector<int>A,B,C;
//判断A,B谁大
bool large(vector<int> &A,vector<int>&B)
{
if(A.size()!=B.size())return A.size()>B.size();//如果A,B的长度不同,选择长度大的
for(int i=A.size()-1;i>=0;i--)
{
if(A[i]!=B[i])return A[i]>B[i];
}
return true;
}
//减法
void subtract(vector<int>&A,vector<int>&B)
{
int t=0;
for(int i=0;i<A.size();i++)
{
t=A[i]-t;
if(i<B.size())t-=B[i];
C.push_back((t+10)%10);//这种写法不需要再判断>=0 or <0
if(t<0)t=1;//当前位不够减,向前借一位
else t=0;
}
while(C.size()>1&&C.back()==0)C.pop_back();//去掉前导0
}
int main()
{
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(large(A,B))//A>B
{
subtract(A,B);
for(int i=C.size()-1;i>=0;i--)cout<<C[i];
}
else//B>A, A-B=-(B-A)
{
subtract(B,A);
cout<<"-";
for(int i=C.size()-1;i>=0;i--)cout<<C[i];
}
return 0;
}
9.高精度乘法
#include<bits/stdc++.h>
using namespace std;
vector<int >mul(vector<int >&A,int b)
{
vector<int >C;
int t=0;//½øλ
for(int i=0;i<A.size()||t;i++)
{
if(i<A.size())t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
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;
}
10.高精度除法
#include<bits/stdc++.h>
using namespace std;
int N=1e6+10;
vector<int >mul(vector<int >&A,int b,int &r)
{
//C=A/b
vector<int >C;
for(int i=A.size()-1;i>=0;i--)
{
r=r*10+A[i];
C.push_back(r/b);
r%=b;
}
reverse(C.begin(),C.end());//翻转数组 3 2 1-> 1 2 3
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');
int r;
vector<int> C=mul(A,b,r);
for(int i=C.size()-1;i>=0;i--)printf("%d",C[i]);
cout<<endl<<r<<endl;
}
五.前缀和
11.前缀和(模版题)
#include <iostream>
using namespace std;
int main()
{
int n, m;
int p, q;
int num[10000000], sum[10000000];
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> num[i];
sum[1] = num[1];
for (int i = 2; i <= n; i++)
{
sum[i] = sum[i - 1] + num[i];
}
while (m--)
{
cin >> p >> q;
// cout << endl;
int cnt = sum[q] - sum[p] + num[p];
cout << cnt<<endl;
}
// cout << endl;
return 0;
}
12.子矩阵的和
#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N], s[N][N];
int main()
{
int 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]);
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
while (q--)
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
}
return 0;
}
13.差分
前缀和和差分其实就是类似求导和积分之间的关系。
a[] ,b[]两个数组
a[i]=b[1]+b[2]+.....+b[i];//a是b的前缀和数组
b[i]=a[i]-a[i-1];//b是a的差分数组
差分主要是作用于对一段连续区间进行操作,如果是for循环逐一操作的话,时间复杂度是O(n)
但我们对b[l]+n,b[r+1]+n,两个端点加n的话,a[l]~a[r]整个区间都加n,时间复杂度是O(1)
#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]; //前缀和运算
printf("%d ", a[i]);
}
return 0;
}
14.差分矩阵
差分矩阵其实与前缀和子矩阵的和类似
差分就是三步走
1.构造差分
2.确定端点
3.进行前缀和运算
#include <iostream>
using namespace std;
const int N = 1e3 + 10;
int n, m, q;
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(){
// ios::sync_with_stdio(false); //提高cin读取速度,不能使用scanf
scanf("%d%d%d", &n, &m, &q); // n行m列,q次
for(int i = 1; i <= n; i++){
for (int j =1; j <= m; j++){
scanf("%d", &a[i][j]);
}
}
// 构建差分数组
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
b[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
}
}
while (q--){
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d", &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++){
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
}
}
for(int i = 1; i <= n; i++){
for (int j =1; j <= m; j++){
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
六:双指针
双指针算法一般有两种形式
//第一种,同时从左边开始遍历
for(int i=0,j=0;i<n;i++)
//j一直往右边走,一直到满足某种条件时,才更新i
//第二种,i从左边开始,j从右边开始
for(int i=0,j=n;i<j;j-- or i++)
14.最长连续不重复子序列
#include<iostream>
using namespace std;
const int N=1e5+10;
int count[N];//¼Ç¼ÔÚi֮ǰÿ¸öÊý³öÏֵĴÎÊý
int a[N];//Ä¿±êÊý×é
int main()
{
int n,ans=0;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=0,j=0;j<n;j++)
{
count[a[j]]++;//统计a[j]出现的次数
while(count[a[j]]>1) //a[j]出现次数大于1
{
count[a[i]]--;
i++;
}
ans=max(ans,j-i+1);
}
cout<<ans<<endl;
return 0;
}
15. 数组元素的目标和
#include<iostream>
using namespace std;
const int N = 1e5+10;
int a[N],b[N];
int n,m,x;
int main()
{
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,j=m-1;i<n;i++)
{
while(j>0&&a[i]+b[j]>x)j--;
if(a[i]+b[j]==x)
{
cout<<i<<' '<<j<<endl;
return 0;
}
}
}
16.判断子序列
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m;
int a[N],b[N];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<m;i++)cin>>b[i];
int i=0,j=0;
while(i<n&&j<m)
{
if(a[i]==b[j])i++;
j++;
}
if(i==n)puts("Yes");
else puts("No");
return 0;
}
七:位运算
17. 二进制中1的个数
#include<iostream>
using namespace std;
int n;
int cnt[100010],a[100010];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)
{
int k=a[i];
while(k)//位运算
{
if(k&1)cnt[i]++;//k&1表示k的二进制数的末尾与1,1&1=1,0&1=0
k=k>>1;//k的二进数右移一位
}
cout<<cnt[i]<<' ';
}
}
八:区间合并
18.区间合并(模版题)
#include<iostream>
#include<algorithm>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
PII s[100100];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i].x>>s[i].y;
}
sort(s+1,s+n+1);
int l=s[1].x,r=s[1].y;
int res1=0,res2=0,cnt=0;
for(int i=2;i<=n;i++)
{
if(s[i].x<=r)r=max(r,s[i].y);
else
{
l=s[i].x,r=s[i].y;
cnt++;
}
// cout<<cnt<<endl;
//cout<<l<<' '<<r<<endl;
}
cout<<cnt+1<<endl;
return 0;
}
九:离散化
19.区间和
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 300010; //n次插入和m次查询相关数据量的上界
int n, m;
int a[N];//存储坐标插入的值
int s[N];//存储数组a的前缀和
vector<int> alls; //存储(所有与插入和查询有关的)坐标
vector<pair<int, int>> 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) r = mid;
else l = mid + 1;
}
return r + 1;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
int x, c;
scanf("%d%d", &x, &c);
add.push_back({x, c});
alls.push_back(x);
}
for (int i = 1; i <= m; i++) {
int l , r;
scanf("%d%d", &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);
printf("%d\n", s[r] - s[l-1]);
}
return 0;
}
十.单链表
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int head,e[N],ne[N],idx;
//初始化
void init()
{
head=-1;
idx=0;
}
//头插法
void add_to_head(int x)
{
e[idx]=x;
ne[idx]=head;
head=idx;
idx++;
}
//尾插法
void add(int k,int x)
{
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
//删除k后面的数
void remove(int k)
{
ne[k]=ne[ne[k]];
}
int main()
{
int m;
cin>>m;
init();
while(m--)
{
int k,x;
char op;
cin>>op;
if(op=='H')
{
cin>>x;
add_to_head(x);
}
else if(op=='D')
{
cin>>k;
if(!k)head=ne[head];
remove(k-1);
}
else if(op=='I')
{
cin>>k>>x;
add(k-1,x);
}
}
for(int i=head;i!=-1;i=ne[i])cout<<e[i]<<" ";
cout<<endl;
return 0;
}