目录
一.归并排序模板:
思路:
(1)归并排序就是先找到一个位置基准,将数组一分为二;
(2)之后分别递归左边和右边区域
(3)然后将两个指针分别指向左边区域与右边区域的起始位置,比较两边指针所指元素的大小,
将小的元素放入新建的tmp数组中,直到一边到达边界,之后把剩余的元素拷贝到tmp数组即可
(4)最后再把tmp数组中排完序之后的元素传入原先的数组中
归并排序模板代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],tmp[N];
void merge_sort(int q[],int l,int r){
if(l>=r) return ;
int mid=(l+r)>>1;
int i=l,j=mid+1,k=0;
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(i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
merge_sort(a,1,n);
for(int i=1;i<=n;i++) cout<<a[i]<<" ";
}
二.二分(需要满足单调性)(整数二分和小数二分)
《1》整数二分:
当可以分成两个部分的时候就可以用二分
【1】向左更新向下取整,不需要+1;
【2】向右更新需要+1,向上取整;
二分模板:
1.找中间值 mid = (l+r+1)/2 or mid = (l+r)/2
2.if(check(mid))等于true或者是false
3.check(m)是检查m是在不满足性质的区间(检查是不是在红色区间)
更新l或者r
(1)从右边二分,找左边界---即在一个递增的数组中找到>=x的第一个位置:
代码:
一直对区间二分,让r左移,因为(l+r)/2一直是下取整,所以r=mid所在位置会一直在左边,也就是最终r会到<=x的第一个位置,因此最后r就是答案
while(l<r){
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
cout<<r<<endl;
(2)从左边二分,找右边界---在一个递增的数组中找到<=x的最后一个位置:
代码:
一直对区间二分,让l向右移动,因为(l+r)/2一直是下取整,所以如果mid取(l+r)/2的话,当l与r相邻并且l在r的左边的时候,就会出现(l+r)/2==l的情况,也就是会l==l==mid,陷入死循环,因此需要在l+r上右取整,使l能够向右移动,即需要写成(l+r+1)
int l=0,r=n-1;
while(l<r){
int mid=(l+r+1)/2;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l<<endl;
《2》小数二分:
由于精度问题,因此l不能写成l=mid+1;
while (r - l > 1e-8)
{
double mid = (l + r) / 2;
if (mid * mid * mid >= x) r = mid;
else l = mid;
}
printf("%.6lf\n", l);
三.高精度加减乘除
1.<加法>
//高精度加法
#include<iostream>
using namespace std;
#include<vector>
//高精度加法函数模板
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]; //到这里算的是A[i]+B[i]+t
C.push_back(t%10); //把数依次压入C中
t/=10; //这里算的进位是多少
}
if(t) C.push_back(1); //如果最后t还有进位,则在最后面再压入一位1
return C;
}
//主函数
int main(){
string a,b;
vector<int>A,B;
cin>>a>>b; //ab是字符串,因此需要用a[i]-‘0’来转换成数字
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0'); //将ab分别反转放入AB容器中
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
vector<int> C=add(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i]; //输出的时候反向输出容器C即可
}
2.《减法》
//高精度减法
#include<iostream>
#include<vector>
using namespace std;
//判断是否A>B
bool cmp(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;
}
//高精度减法函数模板
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; //A[i]-B[i]-t(其中t是借位)
if(i<B.size()) t-=B[i]; //B!=0时,用A[i]-B[i]-t;B=0时,用A[i]-t
C.push_back((t+10)%10); //有t<0和t>=0两种情况,因此用模来表示
if(t<0) t=1;
else t=0;
}
while(C.size()>1&&C.back()==0) C.pop_back(); //当位数>1时,去掉前导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');
//判断是否A>B,如果是就用A-B,否则用B-A并且在前面加个-号
if(cmp(A,B)){
auto C=sub(A,B);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else{
auto C=sub(B,A);
printf("-");
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
}
3.《乘法》
//高精度乘法
#include<iostream>
#include<vector>
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; //如果A还有位数,还能×b就乘
C.push_back(t%10); //取余,依次写出C的各位上的数
t/=10; //需要进位的数
}
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');
vector<int> C=mul(A,b);
for(int i=C.size()-1;i>=0;i--) cout<<C[i];
}
4.《除法》
//高精度除法
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//高精度除法函数模板
vector<int> div(vector<int>A,int b,int &r){ //形参多了一个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()); //算完之后把C反过来输出
while(C.size()>1&&C.back()==0) C.pop_back(); //除去前导0
return C;
}
//主函数
int main(){
string a;
vector<int>A;
int b;
cin>>a>>b;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
int r;
vector<int>C=div(A,b,r);
for(int i=C.size()-1;i>=0;i--) cout<<C[i]; //输出商
cout<<endl<<r<<endl; //输出余数
}
四.《1》一维前缀和与差分数组:
【1】前缀和:
for(int i=1;i<=n;i++) scanf("%d",&a[i]); //都从1开始输入,便于后面的l-1好做,防止溢出
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];//s是前缀和数组,a是原数组
int l,r;
cin>>l>>r;
cout<<s[r]-s[l-1]<<endl;
为了便捷,也可以减少一个数组,将原数组直接变为前缀和数组:
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]+=a[i-1];
}
int l,r;
cin>>l>>r;
cout<<a[r]-a[l-1]<<endl;
【2】差分数组:
原数组为a[],需要构造一个差分数组,使得b[i]=a[i]-a[i-1],从而使得原数组如果进行将a[l]到a[r]均+c的操作的话,就可以通过b[l]+=c,b[r+1]-=c,然后再算出b[i]前缀和即可实现a[l]到a[r]均+c
代码:
//b[i]是差分数组
//a[i]是原数组
for(int i=1;i<=n;i++) {
cin>>a[i];
b[i]=a[i]-a[i-1];
}
while(q--){
int l,r,c;
cin>>l>>r>>c;
b[l]+=c,b[r+1]-=c;
}
for(int i=1;i<=n;i++){
b[i]+=b[i-1];
}
for(int i=1;i<=n;i++) cout<<b[i]<<" ";
《2》子矩阵的前缀和与差分矩阵(二维前缀和与二维差分矩阵)
(通过容斥原理来实现)
【1】子矩阵的前缀和:
(容斥原理)
1.S[i,j]即为图1红框中所有数的的和为:
S[i,j]=S[i,j−1]+S[i−1,j]−S[i−1,j−1]+a[i,j]
2. (x1,y1),(x2,y2)这一子矩阵中的所有数之和为:S[x2,y2]−S[x1−1,y2]−S[x2,y1−1]+S[x1−1,y1−1]
可以将s与a数组都通过a数组来表示
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[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++)
{
cin>>a[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;
cin>>x1>>y1>>x2>>y2;
cout<<a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]<<endl;
}
}
【2】差分矩阵:
原数组是a[i],需要构造差分数组b[i],使得改变b[i]可以使得a[i]某一个范围的元素+c
即构造的差分数组b[i]如下图所示:
构造的差分数组函数为:
void insert(int x1,int y1,int x2,int y2,int c)
{
//对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
代码块思路:
(1)输入原二维数组a[i][j]
(2)将a[i][j]插入b[i][j]
(3)按照题目要求将某一部分+c或-c
(4)计算出b[i][j]前缀和,此时计算出的前缀和矩阵b[i][j]即为所求二维前缀和
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int a[N][N],b[N][N];
//差分数组将某一部分+c或-c构造过程
void insert(int x1,int y1,int x2,int y2,int c){
b[x1][y1]+=c;
b[x1][y2+1]-=c;
b[x2+1][y1]-=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];
//将a[i][j]插入到b[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++){
cout<<b[i][j]<<' ';
}
cout<<endl;
}
}
五.双指针算法:(找单调性优化)
双指针一般题目框架:
for (int i = 0, j = 0; i < n; i ++ )
{
其他条件
while (j的范围&&指针移动所满足的条件) j++ ;
其他条件
}
(1)在一个数组中使用双指针算法:
典型题目链接:最长连续不重复子序列活动 - AcWing
题目思路:
(1)题目中让找的是最长的不重复子序列,因此首先我们需要找到不重复子序列,之后不断更新
更长的子序列长度即可;
(2)j在i的右边,[j,i-1]一定是不重复的子序列,如果第i个元素重复了,则将j一直向右移动,直到第i个元素不重复即可;
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],s[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int res=0;
for(int i=1,j=1;i<=n;i++){
s[a[i]]++;
while(j<=i&&s[a[i]]>1) --s[a[j++]];//s[a[j]]先减去1,然后j再向右移动一个单位
res=max(res,i-j+1);
}
cout<<res<<endl;
}
(2)在两个数组中的双指针算法:
典型题目(1):活动 - AcWing数组元素的目标和
由于a,b都是单调递增的,因此就可以一个指针位于a的起始点,另一个指针位于终止点,然后如果大了就将指针向小的地方移动,如果小了就将指针向大的方向移动
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main(){
long long n,m,x;
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--;
//i从a左开始,j从b的右边开始,如果i和j加起来大于x,j就向左移动,因为ab都是单调的
if((a[i]+b[j])==x) {
cout<<i<<" "<<j<<endl;
break;
}
}
}
典型题目(2):活动 - AcWing判断子序列
题目思路:
(1)题目中有两个数组,可以按照模板一样写
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int j=1;j<=m;j++) cin>>b[j];
bool flag=0;
for(int i=1,j=1;i<=n;i++){
while(j<=m&&a[i]!=b[j]) j++;
if(j==m+1){
flag=1;
break;
}
j++;
}
if(flag) cout<<"No\n";
else cout<<"Yes\n";
}
六.位运算(lowbit运算)
例如:101011000进行lowbit运算之后结果为1000
lowbit运算会找出最右边的1,lowbit具体实现代码如下:(x&-x)
int lowbit(int x){
return x&-x; //x&-x就是x&(~x+1),就是找出最右边的1
}
因此,如果想要求出一个数的二进制中1的个数,就可以每次减去最右边的1,直到此数为0
代码如下:
while(a[i]) a[i]-=lowbit(a[i]),cishu++; //每次减去lowbit,也就是减去最右边的1
七.离散化(重点!!!)
acwing题目链接活动 - AcWing(区间和)
本题让求的是l--r区间和的大小,而l与r的数据范围超过了数组的容量,但是操作数即题目中所涉及到的数最多只有10的5次方,没有超过数组容量大小,因此需要将所涉及到的数分别映射到下标为1--n的数组中,然后再用前缀和即可。
新知识:
对vector数组的遍历可以用一下方式:
for(auto q:qujian){
int l=find(q.first),r=find(q.second);
cout<<s[r]-s[l-1]<<endl;
}
vector通过pair记录坐标的方式:
typedef pair<int,int>PII
vector<PII>add;
原数组最好用vector来存,便于遍历,排序和去重
离散化代码块思路:
(1)将原数组进行排序和去重,使之能够按照顺序依次离散化到a[]数组中
(2)构建find(x)函数,x是离散化前(即原数组)的值,find(x)就是离散化后的下标
find(x)函数通过二分来写,返回的是x离散化前原数组中x的下标+1的大小(+1是为了让x离散化后下标从1开始)
find(x)函数代码如下:
int find(int x){
int l=0,r=alls.size()-1;
while(l<r){
int mid=(l+r)/2;
if(alls[mid]>=x) r=mid;
else l=mid+1;
}
return r+1;
}
(3)通过原数组的值找到离散化后的坐标,然后在离散化后的a[]数组中进行相应操作
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N=1e6+10;
int a[N],s[N];
vector<int>alls;
vector<PII>add,qujian;
//二分找下标
int find(int x){
int l=0,r=alls.size()-1;
while(l<r){
int mid=(l+r)/2;
if(alls[mid]>=x) r=mid;
else l=mid+1;
}
return r+1;
}
int main(){
int n,m;
cin>>n>>m;
//插入
for(int i=0;i<n;i++){
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x);
}
//插入区间
for(int i=0;i<m;i++){
int l,r;
cin>>l>>r;
qujian.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());
//处理插入操作
for(auto u:add){
int x=find(u.first);
a[x]+=u.second;
}
//处理区间和
for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];
//输出对应区间的和
for(auto q:qujian){
int l=find(q.first),r=find(q.second);
cout<<s[r]-s[l-1]<<endl;
}
}
八:区间和并(贪心+模拟+离散化)
acwing题目链接活动 - AcWing
本题涉及到了vector数组的遍历,对于区间边界的比较,以及通过pair来防止l和r超过数组容量,本质上也是离散化
题目思路:
(1)将左右区间分别传入vector<pair>数组
(2)按照左边从小到大排序之后,分别比较左右边界,从而将满足条件的l和r放入vector数组中,
最后数组大小就是满足不相交区间的数量
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
vector<PII>daan;
//传入vector数组
void da(vector<PII> &daan){
vector<PII> res;
sort(daan.begin(),daan.end());
int st=-2e9,ed=-2e9;
for(auto q:daan){
if(ed<q.first){
if(st!=-2e9) res.push_back({st,ed});
st=q.first,ed=q.second;
}
else ed=max(ed,q.second);
}
if(st!=-2e9) res.push_back({st,ed});
//将res数组传给daan数组
daan=res;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
int l,r;
cin>>l>>r;
daan.push_back({l,r});
}
da(daan);
cout<<daan.size();
}