算法基础课
基础算法
快速排序
快速排序
给定你一个长度为 n的整数数列。请你使用快速排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。第二行包含 n个整数(所有整数均在 1∼109范围内),表示整个数列。
输出格式
输出共一行,包含 n个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
#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)/2],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()
{
cin>>n;
for(int i=0;i<n;++i)cin>>q[i];
quick_sort(q,0,n-1);
for(int i=0;i<n;++i)cout<<q[i];
return 0;
}
第K个数
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k个数。
输入格式
第一行包含两个整数 n
和 k。
第二行包含 n个整数(所有整数均在 1∼109范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 k小数。
数据范围
1≤n≤100000,1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
#include <iostream>
using namespace std;
const int N=1e6+10;
int q[N];
int n,k;
void quick_sort(int q[],int l,int r)
{
if(l>=r)return;
int x=q[(l+r)/2],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()
{
cin>>n>>k;
for(int i=0;i<n;++i)cin>>q[i];
quick_sort(q,0,n-1);
cout<<q[k-1];
return 0;
}
归并排序
归并排序
给定你一个长度为 n的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n个整数(所有整数均在 1∼109范围内),表示整个数列。
输出格式
输出共一行,包含 n个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
#inlcude <iostream>
using namespace std;
const int N=1e6+10;
int q[N],tmp[N];
int n;
void merge_sort(int q[],int l,int r)
{
if(l>=r)return;
int mid=l+r>>1;
int k=0,i=l,j=mid+1;
merge_sort(q,l,mid),merge_sort(q,mid+1,r);
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(int i=l,j=0;i<r;++i,++j)q[i++]=tmp[k++];
}
int main()
{
cin>>n;
for(int i=0;i<n;++i) cin>>q[i];
merge_sort(q,0,n-1);
for(int i=0;i<n;++i) cout<<q[i];
}
逆序对的数量
给定一个长度为 n的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤100000
输入样例:
6
2 3 4 5 6 1
输出样例:
5
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
int n;
int q[N], tmp[N];
ll ans;
void merge_sort(int l, int r) {
if (l >= r) {
return;
}
int res = 0;
int mid = (l + r) >> 1;
merge_sort(l, mid), merge_sort(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++];
ans += mid - i + 1;
}
}
while (i <= mid) {
tmp[k++] = q[i++];
}
while (j <= r) {
tmp[k++] = q[j++];
}
for (int i = l, j = 0; i <= r; i++, j++) {
q[i] = tmp[j];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> q[i];
}
merge_sort(0, n - 1);
cout << ans << endl;
return 0;
}
基本思路:
1、左边内部逆序对数量:merge_sort(l,mid)
2、右边内部逆序对数量:merge_sort(mid+1,r)
3、分散在两边:若左侧数字大于右侧数字,存在逆序对为mid-i+1本题有3点需要注意:
1、本题是一个分治算法的使用,先分治后归并。
2、答案是超过int范围的,所以要使用long long
3、提高效率的写法:ios::sync_with_stdio(false)的使用
二分
整数二分
第一个模型----找到的在右侧
check(mid): mid=(l+r+1)/2(此处+1可以避免死循环)
TRUE:[mid,r] l=mid
FALSE:[l,mid-1] r=mid-1第二个模型----找到的在左边
check(mid):mid=(l+r)/2
TRUE:[l,mid] r=mid
FALSE:[mid+1,r] l=mid+1
给定一个按照升序排列的长度为 n 的整数数组,以及 q个查询。对于每个查询,返回一个元素 k的起始位置和终止位置(位置从 0开始计数)。如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 n和 q,表示数组长度和询问个数。第二行包含 n个整数(均在1∼10000范围内),表示完整数组。接下来 q行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q行,每行包含两个整数,表示所求元素的起始位置和终止位置。如果数组中不存在该元素,则返回 -1 -1。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
#include <iostream>
using namespace std;
const int N=1e6+10;
int n,m;
int q[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)scanf("%d",&q[i]);
while(m--)
{
int x;
scanf("%d",&x);
int l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(q[mid]>=x) r=mid;
else l=mid+1;
}
if(q[l]!=x)cout<<"-1 -1"<<endl;
else
{
cout<<l<<" ";
int l=0,r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(q[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
浮点二分法
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6位小数。
数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000
#include <iostream>
using namespace std;
int main()
{
double x;
cin>>x;
double l=-10000,r=10000;
while(r-l>10e-8)
{
double mid=(r+l)/2;
if(mid*mid*mid>=x)r=mid;
else l=mid;
}
printf("%lf\n",l);
return 0;
}
check(mid):mid=(l+r)/2
TRUE:[l,mid] r=mid
FALSE:[mid+1,r] l=mid
精度控制:10^6 对应10^-8
高精度
数字长度<10^6的数字称为高精度数字
高精度加法
给定两个正整数,计算它们的和。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的和。
数据范围
1≤整数长度≤100000
输入样例:
12
23
输出样例:
35
#include <iostream>
#include <vector>
using namespace std;
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=t/10;
}
if(t) C.push_back(1);
return C;
}
int main()
{
string a,b;
cin>>a>>b;
vector<int> A,B;
for(int i=a.size();i>=0;--i) A.push_back(a[i]);
for(int i=b.size();i>=0;--i) B.push_back(b[i]);
auto C=add(A,B);
for(int i=C.size()-1;i>=0;i--)printf("%d",C[i]);
return 0;
}
1、123456存入数组的时候是654321,因为进位的时候可以方便补位
2、模拟人进行加法计算,t表示进位。
高精度减法
给定两个正整数,计算它们的差,计算结果可能为负数。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的差。
数据范围
1≤整数长度≤105
输入样例:
32
11
输出样例:
21
#include <iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A,vector<int> &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> sub(vector<int> &A,vector<int> &B)
{
vector<int> C;
for(int i=0,t=0;i<A.size();i++)
{
t=A[i]-t;
if(i<B.size()) t-=B[i];
C.push_back((t+10)%10);
if(t<0)t=1;
else t=0;
}
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');
if(cmp(A,B))
{
auto C=sub(A,B);
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
}
else
{
auto C=sub(B,A);
printf("-");
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
}
return 0;
}
1、首先需要比较两个数字的大小,大的减去小的,然后再考虑正负号。
2、其次考虑一下减法的具体操作,借位t,如果相减为负数需要借位t为1否则为0。
3、最后需要将类似0001结果的多余0进行删除。
高精度乘法
给定两个正整数 A 和 B,请你计算 A×B的值。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共一行,包含 A×B的值。
数据范围
1≤A的长度≤100000,0≤B≤10000
输入样例:
2
3
输出样例:
6
#include <iostream>
#include <vector>
using namespace std;
vector<int> mult(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_bcak(t%10);
t/=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=mult(A,b);
for(int i=C.size()-1;i>=0;--i) printf("%d",C[i]);
return 0;
}
乘法要理解成:把小数b看作一个整体,然后用A的每一位进行相乘。
高精度除法
给定两个非负整数 A,B,请你计算 A/B的商和余数。
输入格式
共两行,第一行包含整数 A
,第二行包含整数 B。
输出格式
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围
1≤A的长度≤100000,1≤B≤10000,B 一定不为 0
输入样例:
7
2
输出样例:
3
1
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> div(vector<int> &A,int b,int &r)//A/b 余 r
{
vector<int> C;
r=0;
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());
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');
int r;
auto C=div(A,b,r);
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
cout<<endl<<r<<endl;
return 0;
}
除法与其他计算方式有所不一样,是从高位开始进行运算,所以开始得到的结果是顺序的,又因为高位在右侧,所以需要反转一下。
前缀和、差分
前缀和
输入一个长度为 n的整数序列。接下来再输入 m个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l个数到第 r个数的和。
输入格式
第一行包含两个整数 n和 m。
第二行包含 n个整数,表示整数数列。
接下来 m行,每行包含两个整数 l 和 r,表示一个询问的区间范围。
输出格式
共 m行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,1≤n,m≤100000,−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
#include <iostream>
using namespace std;
int const N=10e6+10;
int q[N],s[N];
int m,n;
int main()
{
cin>>m>>n;
for(int i=1;i<=m;++i) scanf("%d",&q[i]);
for(int i=1;i<=m;++i) s[i]=s[i-1]+q[i];
while(n--)
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",s[r]-s[l-1]);
}
return 0;
}
1、下标从1开始。
2、全局变量声明时已经初始化完成了。
子矩阵的和
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,q。
接下来 n行,每行包含 m个整数,表示整数矩阵。接下来 q行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式
共 q行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
#include <iostream>
using namespace std;
const int N=1010;
int a[N][N],s[N][N];
int m,n,q;
int main()
{
cin>>m>>n>>q;
for(int i=1;i<=m;++i)
{
for(int j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=m;++i)
{
for(int j=1;j<=n;++j)
{
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
}
while(q--)
{
int x1,x2,y1,y2;
scanf("%d %d %d %d",&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;
}
差分
差分是前缀和的逆运算,举个例子:
原数组:a1,a2…an
差分数组:a1,a2-a1…an-an-1
作用:当原数组某个区间内[al,ar]要加上同一个数字c的时候,只需要将差分数组的bl+c和b(r+1)-c就好
差分数组的构造方法:将初始化的数组视为0,0,0,…将原数组视为[i,i]之间进行插入运算,插入时使用差分的方式。
输入一个长度为 n的整数序列。接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n和 m。第二行包含 n个整数,表示整数序列。接下来 m行,每行包含三个整数 l,r,c,表示一个操作。
输出格式
共一行,包含 n个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
#include <iostream>
using namespace std;
int const N=1e6+10;
int a[N],b[N];//原数组和差分数组
int n,m;
void insert(int l,int r,int c)
{
b[l]+=c;
b[r+1]-=c;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) insert(i,i,a[i]);
while(m--)
{
int l,r,c;
scanf("%d %d %d",&l,&r,&c);
insert(l,r,c);
}
for(int i=1;i<=n;++i) b[i]+=b[i-1];
for(int i=1;i<=n;++i) printf("%d ",b[i]);
return 0;
}
差分矩阵
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c。请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n行,每行包含 m个整数,表示整数矩阵。接下来 q行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n行,每行 m个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
#include <iostream>
using namespace std;
int const N=1010;
int a[N],b[N];
int n,m;
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][y2]+=c;
}
int main()
{
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]);
puts("");
}
}
puts输出字符串,自带换行。
双指针算法
简单使用
#include <iostream>
#inlcude <string.h>
using namespace std;
int main()
{
char s[1000];
gets(s);
int n=strlen(s);
for(int i=0;i<n;++i)
{
int j=i;
while(j<n&&s[j]!=' ') j++;
for(int k=0;k<j;++k) cout<<s[k];
cout<<endl;
i=j;
}
return 0;
}
最长连续不重复子序列数
给定一个长度为 n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。第二行包含 n个整数(均在 0∼105范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤105
输入样例:
5
1 2 2 3 5
输出样例:
3
#include <iostream>
using namespace std;
const int N=1e5+10;
int n;
int a[N],s[N];
int main()
{
cin>>n;
for(int i=0;i<n;++i)cin>>a[i];
int res=0;
for(int i=0,j=0;i<n;++i)
{
s[a[i]]++;
while(s[a[i]]>1)
{
s[a[j]]--;
j++;
}
res=max(res,i-j+1);
}
cout<<res<<endl;
return 0;
}
位运算
问题一:n的二进制表示中第k位是几
1、先把k位移到最后一位(n>>k)
2、看个位是几(&1)
公式 (n>>k)&1
二进制中1的个数
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1的个数。
输入格式
第一行包含整数 n。第二行包含 n个整数,表示整个数列。
输出格式
共一行,包含 n个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1的个数。
数据范围
1≤n≤100000,
0≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2
#include <iostream>
using namespace std;
int const N=1e5+10;
int a[N];
int n;
int lowbit(int x)
{
return x&-x;
}
int main()
{
cin>>n;
while(n--)
{
int x;
cin>>x;
int res=0;
while(x) x-=lowbit(x),res++;
cout<<res<<" ";
}
return 0;
}
问题二:lowbit(x):返回x的最后一位1
x&(-x)==x&(~x+1)
离散化
第一步:去重
//C++去重模板
vector<int> alls;//存储所有待离散化的值
sort(alls.begin(),alls.end());//将所有值排序
alls.erase(unique(alls.begin(),alls.end()),alls.end());//去掉重复元素
第二步:二分法求出对应离散化的值
int find(int x)//找到第一个大于等于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;//映射到1,2,3...
}
区间合并
给定 n 个区间 [li,ri],要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。输出合并完成后的区间个数。
例如:[1,3]
和 [2,6] 可以合并为一个区间 [1,6]。
输入格式
第一行包含整数 n。接下来 n行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤100000,
−109≤li≤ri≤109
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N=100010;
int n;
vector<PII> segs;
void merge(vector<PII> &segs)
{
vector<PII> res;
sort(segs.begin(),segs.end());
int st=-2e9,ed=-2e9;
for(auto seg:segs)
{
if(ed<seg.first)
{
if(st!=-2e9) res.push_back({st,ed});
st=seg.first,ed=seg.second;
}
else ed=max(ed,seg.second);
}
if(st!=-2e9) res.push_back({st,ed});//防止为空的情况
segs=res;
}
int main()
{
cin>>n;
for(int i=0;i<n;++i)
{
int l,r;
cin>>l>>r;
segs.push_back({l,r});
}
merge(segs);
cout<<segs.size()<<endl;
return 0;
}
1、pair是将两种数据类型结合的语法,类似struct
2、分三种情况,包含、有交集、没交集
3、基本思路:首先将区间按照左端点进行排序,判断右端点和下一个区间的左端点的大小以此来判断是否有交集,最后输出pair区间个数。