基础算法模板总结

一、快速排序

1.1、模板

#include<iostream>
using namespace std;
const int N = 1e5+10; 
int q[N];
int n;

void quick_sort(int q[],int l,int r){
	int mid = q[(l+r)/2];
	int i = l,j = r;
	int temp;
	while(i<j){
		while(q[i]<mid)i++;
		while(q[j]>mid)j--;
		if(i<=j){
			temp = q[i];
			q[i] = q[j];
			q[j] = temp;
			i++;
			j--;
		}
	}
	if(l<j)quick_sort(q,l,j);
	if(i<r)quick_sort(q,i,r);
} 
int main(){
	int array[]={49,38,65,97,76,13,27,49};
	int length=sizeof(array)/sizeof(*array);
	cout<<"原始序列:";
	for(int i=0;i<length;i++){
		cout<<array[i]<<" ";
	}
	cout<<endl;
	quick_sort(array,0,length-1);
	cout<<"快排序列:";
	for(int i=0;i<length;i++){
		cout<<array[i]<<" ";
	}
	return 0;
}

1.2、思路

第一步:确定一个分界点mid(建议取中间数)

第二步:调整区间,让mid左边的数都<=mid,mid右边的数都>=mid

第三步:递归处理左右两边

1.3、例题1:第k个数

在这里插入图片描述

答案1:

#include<iostream>
using namespace std;
const int N=1e5;
int a[N];

void quick_sort(int a[],int l,int r){
	int mid = a[(l+r)/2];
	int i=l,j=r;
	int temp;
	while(i<j){
		while(a[i]<mid)i++;
		while(a[j]>mid)j--;
		if(i<=j){
			temp = a[i];
			a[i] = a[j];
			a[j] = temp;
			i++;
			j--;
		}
		if(j>l)quick_sort(a,l,j);
		if(i<r)quick_sort(a,i,r);
	}
}
int main(){
	int n,k;
	cin>>n>>k;
	int p[n];
	for(int i=0;i<n;i++){
		cin>>p[i];
	} 
	quick_sort(p,0,n-1);
	cout<<p[k-1]<<endl;
	return 0;
}

二、归并排序

2.1、模板

#include<iostream>
using namespace std;
const int N = 1e5+10;
int n;
int q[N],temp[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]) 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 = l, j = 0; i <= r; i++, j++)
        q[i] = temp[j];
}
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]<<" "; 
	}
	return 0;
}

2.2、思路

归并排序的思想是将一个大的序列先进行二分法划分,直到划分到每个字序列只包含一个元素为止。然后对序列进行依次合并,在合并的过程中实现排序。

归并排序是一种典型的外部排序算法,需要额外的辅助空间来完成算法。

2.3、例题1:求逆序对

题目如下
给定一个长度为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;
#include<algorithm>

const int N = 1e5+10;
int q[N];
int n;
int temp[N];
long long ans;

void merge_sort(int l,int r,int q[]){
	if(l>=r)return;
	int mid = l+r>>1;
	merge_sort(l,mid,q);
	merge_sort(mid+1,r,q);
	int i=l,j = mid+1,k=0;
	while(i<=mid && j<=r){
		if(q[i]>q[j]){
			temp[k++] = q[j++];
			ans+=mid-i+1;
		}
		else
		temp[k++] = q[i++];
	} 
	while(i<=mid)temp[k++] =q[i++];
	while(j<=r)temp[k++] = q[j++];
	for (i = l, j = 0; i <= r; i++, j++)
        q[i] = temp[j];
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>q[i];
	ans =0;
	merge_sort(0,n-1,q);
	cout<<ans<<endl;
	return 0;
	
}

三、二分查找

3.1、模板

3.1.1、模板一

将区间[l,r]划分为[l,mid][mid+1,r]时,其继续查找的操作是r=mid或者l=mid+1,计算mid的时候不需要额外加一。

#include<iostream>
using namespace std;
inr binarySearch1(int l,inr ){
	while(l<r){
		int mid = l+r>>1;
		if(check(mid))r = mid;//假设这个数一开始在左边
		else l = mid+1;
	}
	return l;
} 

3.1.2、模板二

将区间[l,r]划分为[l,mid-1][mid,r]时,其继续查找的操作是r=mid-1或者l=mid,为了防止进入死循环,计算mid的时候需要额外加一

#include<iostream>
using namespace std;
int binarySearch2(int l,int r){
	while(l<r){
		int mid = l+r+1>>1;
		if(check(mid)) l = mid;//假设这个数在右边 
		else r = mid-1;
	}
	return l;
}

3.1.3、浮点数二分查找

#include<iostream>
using namespace std;
bool check(double x){
	
}
//eps 表示精度,取决于题目对精度的要求 比有效数精度多2
double binarySearch3(double l,double r){
	const double eps = 1e-6;
	while(r-l>eps){
		double mid = l+r>>1;
		if(check(mid))r = mid;
		else l = mid;
	}
	return l;
}

3.2、思路

将n个元素分成个数大致相同的两半,取q[n/2]与想要查找的元素x作比较,如果找到算法终止。如果没有找到,则分为两种情况:

  • x>q[n/2],则在数组q的右半部继续搜索x;
  • x<q[n/2],则在数组q的左半部继续搜索x;

二分查找的模板一共有两个,视情况进行选择。

3.3、例题

3.3.1、例题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;
int n,q,k;
const int N = 1e5+10;
int a[N];

int main(){
	cin>>n>>q;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	while(q--){
		cin>>k;
		int l=0,r = n-1;
		while(l<r){
			int mid=l+r>>1;
			if(k>a[mid])l=mid+1;
			else
			r = mid;
		}
		if(a[l]!=k)
		cout<<-1<<" "<<-1;
		else{
			cout<<l<<" ";
			int l =0,r = n-1;
			while(l<r){
				int mid = 1+l+r>>1;
				if(k<a[mid]){
					r = mid-1; 
				}
				else
				l = mid;
			} 
			cout<<l<<endl;
		}
	} 
	return 0;
}

3.3.2、例题2:数的三次方根

给定一个浮点数 n,求它的三次方根。

输入格式
共一行,包含一个浮点数 n。

输出格式
共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围
−10000≤n≤10000

输入样例:
1000.00

输出样例:
10.000000

解析:

求n的三次方根,n的范围是[-10000,10000],所以答案and的范围可以初始化为[-10000,10000]

但是,求n的二次方根,n的范围是[0,0.01],答案ans 的范围不能初始化为[0,0.01]

浮点数比较问题:
如果两个浮点数的差的绝对值小于一个很小的数。比如题中的1e-8,因为题目要求小数点后保留6个以上数字。

#include<iostream>
using namespace std;
int main(){
	double n;
	double l = -10000,r = 10000;
	cin>>n;
	while(r-l>=1e-8){
		double mid = (r+l)/2;
		if(mid*mid*mid>=n)r= mid;
		else l = mid;
	}
	cout<<l<<endl;
	return 0; 
}

四、高精度模板

4.1、高精度加法

位数>=10^6

#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() || 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(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 = add(A, B);
    for (int i = C.size() - 1; i >= 0; i--)
        cout<<C[i]<<" ";
    return 0;
} 

4.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();
    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;
    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);
        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;
}

4.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;
        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;
    vector<int> aa;
    cin >> a >> b;
    
    for (int i = a.size() - 1; i >= 0; i--) aa.push_back(a[i] - '0')
        
    auto c = mul(aa, b);
    for (int i = c.size() - 1; i >= 0; i--) 
        printf("%d", c[i]);
    return 0;
}

4.4、高精度除法

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

// A/b 商是C,余数r
vector<int> div(vector<int> &a, int b, int &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> aa;
    for (int i = a.size() - 1; i >= 0; i--) 
        aa.push_back(a[i] - '0');
    int r;
    auto c = div(aa, b, r);
    for (int i = c.size() - 1; i >= 0; i--)
        printf("%d", c[i]);
    // cout << endl << r << endl;
    return 0;
}

五、前缀和

5.1、一维前缀和模板

s[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = s[r] - s[l - 1]
#include<iostream>
using namespace std;
const int N = 1e5+10;
int s[N],a[N];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++)
	cin>>a[i];
	for(int i=0;i<=n;i++)
	s[i] = s[i-1]+a[i-1];
	while(m--){
		int l,r;
		cin>>l>>r;
		cout<<s[r]-s[l-1]<<endl;
	}
	return 0;
}

5.2、二维前缀和模板

s[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]
#include<iostream>
using namespace std;
const int N = 1010;
int s[N][N],a[N][N];
int n,m,q;
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++)
	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;
		cin>>x1>>y1>>x2>>y2;
		cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl;
	}
	return 0;
} 

5.3、例题

5.3.1、例题一

输入一个长度为 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;
const int N = 1e5+10;
int a[N],s[N];
int main(){
	int n,m,l,r;
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	for(int i=0;i<n;i++){
		s[i] = s[i-1]+a[i-1];
	}
	while(m--){
		cin>>l>>r;
		cout<<s[r]-s[l-1]<<endl;
	}
	return 0;
}

5.3.2、激光炸弹

地图上有 N个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式

第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形包含的横纵位置数量,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi,分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式

输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤1090

0<N≤100000,

0≤Xi,Yi≤5000

0≤Wi≤10000

输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
#include<iostream>
#include<stdio.h>
using namespace std;
const int n = 5010;
int s[n][n];
int N,R,x,y,w;
int main(){
	int maxw=0;
	cin>>N>>R;
	R = min(5010,R);
	while(N--){
		cin>>x>>y>>w;
		s[x+1][y+1] += w;
	} 
	for(int i=1;i<5001;i++)
		for(int j=1;j<5001;j++)
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
	for(int i=R;i<5001;i++)
		for(int j=R;j<5001;j++){
			maxw = max(s[i][j] - s[i - R][j] - s[i][j - R] + s[i - R][j - R], maxw);
	}
	cout<<maxw;
	return 0;
} 

六、差分

6.1、一维差分模板

其实就是前缀和的升级版,先看一下差分函数

void insert(int l,int r,int c){
	s[l]+=c;
     s[r+l]-=c;
}
#include<iostream>
using namespace std;

const int N = 1e6+10;
int a[N],b[N];

void insert(int l,int r,int c){
	b[l]+=c;
	b[r+1]-=c;
} 
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		//构造差分数组b
		for(int i=1;i<=n;i++){
			insert(i,i,a[i]);
		} 
	while(m--){
		int l,r,c;
		cin>>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++){
		cout<<b[i]<<endl;
	}
	return 0;
	}
}

6.2、思路

详见例题

6.3、例题

6.3.1、差分

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 1005;
int a[N];
int l,r,c;
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	while(m--){
		cin>>l>>r>>c;
		for(int i=l-1;i<r;i++){
			a[i] = a[i]+c;
		}
	}
	for(int i=0;i<n;i++)
		cout<<a[i]<<" ";
	return 0;
}
#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N],b[N];
//变化一部分数的时候即这段区间两端的b数组即差分会变化
//b[i] = a[i] - a[i - 1]
void insert(int l,int r,int c){
	b[l]+=c;
	b[r+1]-=c;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		insert(i,i,a[i]);
	}	
	while(m--){
		int l,r,c;
		cin>>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++){
		cout<<b[i]<<" "; 
	}
	return 0;
}

6.3.2、子矩阵的和

在这里插入图片描述

【解析】

s[i][j] = 第i行j列格子左上部分所有元素的和

以(x1,y2)为左上角,(x2,y2)为右下角

本道题是求子矩阵的和,首先按照规律将矩阵的每个点的矩阵值算出来,之后再根据相同的规矩来进行计算

S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

#include<iostream>
using namespace std;
int n,m,q;
const int N = 1005;
int s[N][N];
int main()
{
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>s[i][j];
			s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j];
		}
	}
	while(q--){
		int x1,y1,x2,y2;
		cin>>x1>>y1>>x2>>y2;
		cout<<s[x2][y2]+s[x1-1][y1-1]-s[x2][y1-1]-s[x1-1][y2]<<endl;
	}
	return 0; 
} 

6.3.3、差分矩阵(二维差分)

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 10005;
int a[N][N];
int n,m,q;

int 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(){
	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,x2,y1,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][j-1]+b[i-1][j]-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;
	}
	return 0;	
}

七、双指针

7.1、模板

常见问题分类:

(1)对于一个序列,用两个指针维护一段区间

(2)对于两个序列,维护某种次序,比如归排序中合并两个有序序列的操作

for(int i=0,j=0;i<n;i++){
     while(j<i&&check(i,j))j++;
     res = max(res, i - j + 1)
}

7.2、思路

朴素做法

for (int i = 0; i < n; i ++)
    for (int j = 0; j <= i; j ++)
        if (check(j, i))
        {
            res = max(res, i - j + 1);
        }

7.3、例题

7.3.1、最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续区间,输出它的长度。

输入格式

第一行包含整数n。

第二行包含n个整数(均在0~100000范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复数字的连续子序列的长度。

数据范围

1≤n≤1000001≤n≤100000

输入样例:

5

1 2 2 3 5

输出样例:

3

#include<iostream>
using namespace std;
const int N = 100005;
int count[N],a[N];
int n;

int main(){
	cin>>n;
	int res = 0;
	for(int i=0;i<n;i++)cin>>a[i];
	for(int i=0,j=0;i<n;i++){
		//i为终点,j为起点 
		count[a[i]]++;
		//遇到重复的元素,j往后移,同时重复的元素的个数减1 
		while(count[a[i]]>1)count[a[j++]]--;
		//枚举从起点到终点的距离的最大值 
		res = max(res,i-j+1);
	}
	cout<<res;
	return 0;
}

7.3.2、数组元素的目标和

给定两个升序排序的有序数组A和B,以及一个目标值x。数组下标从0开始。

请你求出满足A[i] + B[j] = x的数对(i, j)。

数据保证有唯一解。

输入格式

第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。

第二行包含n个整数,表示数组A。

第三行包含m个整数,表示数组B。

输出格式

共一行,包含两个整数 i 和 j。

数据范围

数组长度不超过100000。

同一数组内元素各不相同。

1≤数组元素≤109

输入样例:

4 5 6

1 2 4 7

3 4 6 8 9

输出样例:

1 1

【解析】

a与b两数组都是升序,用双指针i和j,i先指向a数组的第一个元素,j指向b数组的最后一个元素。因此,当i向右移动时a[i]变大,j向左移动时b[j]变小。

对于a 的每一个i,当a[i]+b[j]大于目标值x时,j就向左移动直到不满足a[i]+b[j]>x。可以将时间复杂度O(n*m)降到O(n+m)

#include<iostream>
using namespace std;
const int N = 10010;
int n,m,x;
int a[N],b[N]; 
int main(){
	cin>>n>>m>>x;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	for(int j=0;j<m;j++){
		cin>>b[j];
	}
	int i=0,j=m-1; 
	//a数组从左边枚举,b数组从右边进行枚举 
	while(i<n&&j>0){
		//和大于x,j向前移动 
		while(a[i]+b[j]>x)j--;
		//和小于x,i向后移动 
		while(a[i]+b[j]<x)i++;
		if(a[i]+b[j]==x){
			cout<<i<<" "<<j;
			break;
		}
	}
	return 0;
} 

八、位运算

8.1、模板

求n的第k位数字:n>>k&1
返回n的最后一位1:lowbit(n) = n & -n;

8.2、思路

n-1:一个二进制的数减1,就是将这个二进制最右边的那个1变成0,然后它后边的所有位置都变成1

让这个数n与n-1按位与再赋给它,意义是,从低位到高位,n的二进制位中第一次出现1的位置变成0(循环),直到n的二进制位上的1全变成0(此时n=0),循环终止

8.3、例题

#include<iostream>
using namespace std;

int main(){
	//输入几个十进制数 
	int n;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		int res = 0;
		while(x){
			x&=(x-1);
			res++;
		} 
		cout<<res<<endl;
	}
	return 0;
} 

九、离散化

9.1、模板

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
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, ...n
}

9.2、思路

当数据范围超过我们数组可以开出的长度,并且,我们的n和m只有五次方,也就是需要操作耳朵数最多只有3*10的五次方个数,远远小于了x的数据范围,于是我们想到了离散化,也就是将所需要操作的数进行了一个排序并且去重。

9.3、例题

在这里插入图片描述

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
// 存储所有下标
vector<int> alls;
// 存储添加 查询操作
vector<PII> add, query;
int a[N], s[N];
// 寻找离散后的索引+1
int find(int x)
{
    int l = 0, r = alls.size() - 1;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        // 离散后的索引+1
        if (alls[mid] == x) return mid + 1;
        else 
        {
            if (alls[mid] < x) l = mid + 1;
            else r = mid - 1;
        }
    }
    return 0;
}
int main()
{
    int n, m;
    cin >> n >> m;
    while (n--)
    {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        //存储所有下标
        alls.push_back(x);
    }
    while (m--)
    {
        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());
    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];
    for (auto item : query)
    {
        int l = find(item.first), r = find(item.second);
        cout << s[r] - s[l - 1] << endl;
    }
    return 0;    
}

十、区间合并

10.1、模板

// 将所有存在交集的区间合并
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;
}
#include<iostream>
#include<vector>
#include<algorithm>
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;
    // 初始化左右边界 
    int st = -2e9, ed = -2e9;
    sort(segs.begin(), segs.end());
    for (auto seg : segs)
    {
        if (ed < seg.first)
        {
            //不把最初的区间(st, ed)放进去
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first;
            ed = seg.second;
        }
        //更新右边界
        else ed = max(ed, seg.second);
    }
    //防止输入的没有任何区间
    //因为之前存储的都是前一个区间,所以最新的,最后一个独立的区间并没有被合并进去,所以要在出循环后在判断一次是否添加最后一个区间,如果这个区间不为空则添加进去
    //本题中 n≥1,所以if可以去掉。但如果 n=0,去掉以后会有问题
    if (st != -2e9) res.push_back({st, ed});
    segs = res;
}
int main()
{
    cin >> n;
    while (n--)
    {
        int l, r;
        cin >> l >> r;
        segs.push_back({l, r});
        
        merge(segs);
        cout << segs.size() << endl;
        return 0;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值