第一篇——基础算法

基础算法篇

快速排序
分治算法都有三步

1.分成子问题

2.递归处理子问题

3.子问题合并

分析:基于分治

1.确定分界点

2.调整区间 左边都小于x,右边都大于x

3.递归处理左右两段

void quick_sort(int q[],int l, int r){
	//递归的终止情况
    if(l >= r )return ;
	int i = l-1,j= r+1,x = q[l + r>>1];
	while(i < j){
		do i++; while(q[i] < x);
		do j--; while(q[i] > x);
		if(i < j ) swap(q[i],q[j]);
		
		}
}	
	//递归处理子问题
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
	
	//子问题合并,快排这里不需要,但归并排序的核心在这一步骤
}
使用STL
#include<bits/stdc++.h>
using namespace std;

const int N = 100000010;
int a[N],n,m,i,j;
int main(){
	ios :: sync_with_stdio(false);
	cin >> n;
	for(int i = 1; i <= n;i++){
		cin >> a[i];
	}
	sort(a+1,a+1+n);
	for(int i = 1;i<=n;i++){
		cout << a[i] << " ";
	
	}
		cout << endl;
		return 0;
}
求k个数
#include<iostream>
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int n ,k;
int q[N];

int quick_sort(int l ,int r,int k)
{
	if(l==r) return q[l];
	
	int x = q[l],i = l - 1,j = r + 1;	
	while(i<j)
	{
		while(q[++i] < x);
		while(q[--j] > x);
		if(i < j) swap(q[i],q[j]);	
	}
	int s1 = j-l+1;
	if(k <= s1)quick_sort(l,j,k);
	else quick_sort(j+1,r,k-s1); 
} 

int main()
{
	cin >> n >> k;
	for(int i = 0;i < n ;i++)
	{
		scanf("%d",&q[i]);	
	}	
	cout << quick_sort(0,n-1,k) << endl;
	return 0;
}

归并排序
#include<iostream>
using namespace std;

const int  N = 100010;

int n ,a[N],temp[N];

void merge_sort(int l,int r)
{
	//递归边界
	if(l >= r) return ;
	//步骤1 确定分界点 mid
	int mid  = l + r >>1;
	//步骤2  递归左右区间
	merge_sort(l,mid),merge_sort(mid +1,r);
	//步骤3   
	int i = l,j = mid +1;
	int k  = l;
	while(i  <= mid && j <= r)   
	if(a[i] <= a[j]) temp[k++] = a[i++];
	else temp[k++] = a[j++];
	
	while(i <= mid) temp[k++] = a[i++];
	while(j <= r)   temp[k++] = a[j++];
	
	//复制回原数组
	for(int i = l;i<=r;i++)
	a[i] = temp[i]; 
}


int main()
{
	cin >> n;
	for(int i = 0;i < n; i++) cin >> a[i];
	merge_sort(0,n-1);
	for(int i = 0;i < n; i++) cout << a[i] <<" ";
}
二分
数的范围
#include <iostream>
using namespace std;
const int maxn = 100005;
int n, q, x, a[maxn];
int main() {
    scanf("%d%d", &n, &q);
    for (int i = 0; i < n; i++)    scanf("%d", &a[i]);
    while (q--) {
        scanf("%d", &x);
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (a[mid] < x)  l = mid + 1;
            else    r = mid;
        }
        if (a[l] != x) {
            printf("-1 -1\n");
            continue;
        }
        int l1 = l, r1 = n;
        while (l1 + 1 < r1) {
            int mid = l1 + r1 >> 1;
            if (a[mid] <= x)  l1 = mid;
            else    r1 = mid;
        }
        printf("%d %d\n", l, l1);
    }
    return 0;
}

数的三次方根
#include<iostream>
#include<iomanip>
using namespace std;
double n,l,r,mid;
bool flag;
double q(double a){return a*a*a;}
int main(){
    cin>>n;
    l=-10000,r=10000;
    while(r-l>=1e-7){
        mid=(l+r)/2;
        if(q(mid)>=n) r=mid;
        else l=mid;
    }
    cout<<fixed<<setprecision(6)<<l;
    return 0;
}
高精度
高精度加法

1.变量实现

#include<iostream>
#include<vector>

using namespace std;
vector<int> sum (vector<int>& a,vector<int>& b)
{
	vector<int> result;
	if(a.size()<b.size()) return sum(b,a);
	int  t = 0;//进位
	for(int i = 0;i < a.size() || t; i++)  //a.size() >= b.size()
	{
			if(i<a.size()) t += a[i];
			if(i<b.size()) t += b[i];
			result.push_back(t%10);
			t /= 10;
	} 
	return result;
}
int main()
{
	string a,b;
	vector<int> c,d;
	vector<int> result;//存放结果
	cin >> a>>b;
	//按个位,十位 ,百位,···n位存放
	for(int i = a.size()-1; i>=0 ;i--) c.push_back(a[i]-'0');//将字符a[i]转换成数值
	
	for(int i = b.size()-1; i>=0 ;i--) d.push_back(b[i]-'0');//将字符串b[i]转换成数值
	
	result = sum(c,d);
	for(int i = result.size()-1;i>=0;i--)	cout << result[i];
	
	return 0;
}

2.数组实现

//数组实现
#include<iostream>
using namespace std;
const int N = 100010;

int A[N],B[N],C[N];

int Add(int a[],int b[],int c[],int cnt){
	int t = 0;//t表示进位 
	for(int i = 0;i<=cnt;i++){
		t += a[i] + b[i];//进位加上a和b第i位上的数
		c[i] = t %10;  //c的值就是进位的个位数
		t /=10;		//把t的个位数去掉只剩下十位数,即只剩下这个位置的进位 
		 
	}
	if(t) c[++cnt] = 1;//如果t==1,表示还有一个进位,要补上
	return cnt; 
}

int main(){
	string a,b;
	cin >> a>>b;
	
	//a和b倒着放进int数组 ,因为有进位,倒着放容易处理
	int cnt1 = 0;
	for(int i = a.size()-1;i>=0;i--)
	A[++cnt1] = a[i] - '0';
	
	int cnt2  =0;
	for(int i = b.size()-1;i>=0;i--)
	B[++cnt2] = b[i]-'0';
	
	int tot = Add(A,B,C,max(cnt1,cnt2));
	
	
	//因为A和B要倒着放,所以C也要到着输出
	for(int  i = tot;i>=1;i--)
	cout << C[i];	 
}
 
高精度减法

1.变量实现

#include<iostream>
#include<bits/stdc++.h>

#include<vector>

using namespace std;
const int N = 1000010;

bool cmp(vector<int> &A,vector<int> &B){
	if(A.size() != B.size())  return A.size() >= B.size();
	for(int i = A.size();i >=0  ;i++){
		if(A[i] != B[i])
		return A[i] > B[i];
			
		return true;	
	}
}

void trimZero(vector<int> &A)
{
	while(A.back() == 0 && A.size() > 1) A.pop_back();
} 

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 ;
	}
	trimZero(C);
	 
	 return C;
}

int main(){
	string a,b;
	cin >> a >> b;
	vector<int> A,B,C;
	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');
	
	trimZero(A),trimZero(B);
	if(cmp(A,B))  C = sub(A,B);
	else{
		C = sub(B ,A);
		printf("-");
	}
	for(int i  = C.size()-1 ; i >=0; i--) cout << C[i];
	return 0;
}



2.数组实现


高精度乘法

高精度 * 低精度

#include<bits/stdc++.h>
#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() ; i++){
		t += A[i] * b;
		C.push_back(t % 10);
		t /= 10;
	}
	
	while(t) {
		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;
	vector<int> C;
	for(int i = a.size()-1; i >= 0 ;i --){
		A.push_back(a[i] - '0');
	} 
	 C = mul(A,b);
	
	for(int i = C.size()- 1; i>= 0 ;i--){
		cout << C[i];
	}
}

高精度* 高精度

#include<iostream>
#include<vector>

using namespace std;

vector<int> mul(vector<int> & A, vector<int> & B){
	vector<int> C(A.size() + B.size(),0);//初始化为0,且999*99 最多5位
	
	for(int i = 0;i < A.size();i++ ){
		for(int j = 0;j < B.size();j++){
			C[i+j] += A[i] * B[j];
		}
	}
	
	int  t= 0;
	for(int i = 0;i < C.size();i++ ){
		//i= C.size() - 1时,t一定小于10
		t += C[i];
		C[i] = t % 10;
		t /= 10; 
		
	} 
	
	while(C.size() > 1 && C.back() == 0) C.pop_back();//必须要去前导0,因为最高位很可能时0
	return C; 
}

int main(){
	string a,b;
	cin >> a >> b;
	
	vector <int > A,B,C;
	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');
	}
	
	C=  mul(A,B);
	for(int i = C.size() -1 ;i >= 0 ;i--){
		cout << C[i];
		
	}
	return 0;
}


高精度除法
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

//int r =0;
vector<int> div (vector<int> & A, int B ,int  &r){//r传入r的地址,便于直接对余数r进行修改 
	vector<int> C;
	for(int i = 0;i < A.size();i++){
		r = r* 10+A[i];
		C.push_back(r/B);
		r = r % B; 
	}
	//由于在除法运算中,高位到低位运算,因此C的前导零都在vector的前面而不是尾部,vector只有
	//删除最后一个数字的pop_back是常数复杂度,而对于删除第一位没有相应的库函数可以使用,
	//而且删除第一位,其余位也要前移   因此我们将C反转,这样0就位于数组的尾部,可以使用pop
	//函数删除前导零 
		reverse(C.begin(),C.end());
		while(C.size() > 1 && C.back() == 0) C. pop_back();
		return C;
}
int main(){
	string a;
	int B, r =0;
	cin >> a >> B;
	vector<int> A;
	vector<int> C;
	for(int i = 0;i<a.size();i++) A.push_back(a[i] - '0');//注意这次的A是由高位传输至低位,由于
	//除法的运算过程中,发现从高位进行处理
	 C = div(A,B,r);
	for(int i = C.size()-1; i >= 0; i--) cout << C[i];
	cout << endl << r;
	cout << endl;
	return 0;  
}

















前缀和与差分
前缀和
#include <iostream>
#include <vector>

using namespace std;

int s;int e;
int n,m;
const int N = 100100; 
vector<int> v(N,0);
vector<int> preSum(N,0);
vector<pair<int,int>> vp;
int main()
{
    cin >> n >> m;

    for(int i =1;i <= n;i++){
        cin >> v[i];
        preSum[i] = preSum[i-1]+v[i];
    }

    while(m--){
        cin >>s >> e;
        cout << preSum[e] -  preSum[s-1]  << endl;
    }


    return 0;
}


二维数组求前缀和
#include <bits/stdc++.h>
 
using namespace std;
 
const int MAXN = 1e3+2;
const int MAXM = 1e3+2;
int sum[MAXN][MAXM] = {};
 
int main() {
	int n,m,r,c;
	cin>>n>>m;//>>r>>c;
	
	int data;
	for (int i=1; i<=n; i++) {
		for (int j=1; j<=m; j++) {
			cin >> data;
			sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+data;
		}
	}
	
	for (int i=1; i<=n; i++) {
		for (int j=1; j<=m; j++) {
			cout << sum[i][j] << " ";
		}
		cout << endl;
	}
	
	return 0;
}

差分

#include<iostream>
#include<vector>
using namespace std;
vector<int> s(100010,0),b(100010,0);
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>s[i];//存放原数组
    // for(int i=1;i<=n;i++) cout<<s[i]<<' ';
    // cout<<endl;

    for(int i=1;i<=n;i++) b[i] = s[i]-s[i-1];//构造差分数组
    // for(int i=1;i<=n;i++) cout<<b[i]<<' ';
    // cout<<endl;
    while(m--){
        int l,r,c;
        cin>>l>>r>>c;
        b[l]+=c;//将l和以后加c
        b[r+1]-=c;//将r之后-c
    }
    // for(int i=1;i<=n;i++) cout<<b[i]<<' ';
    // cout<<endl;
    for(int i=1;i<=n;i++){
        b[i]=b[i-1]+b[i];//将差分改为原数组
        cout<<b[i]<<' ';
    }
    return 0;
}


差分矩阵

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N = 1e3+5;
int a[MAX_N][MAX_N] = {0};
int s[MAX_N][MAX_N] = {0};
void insert(int x1,int y1,int x2,int y2,int v){
    a[x1][y1] += v;
    a[x2+1][y1] -= v;
    a[x1][y2+1] -= v;
    a[x2+1][y2+1] += v; //多减一次,加v进行抵消操作
}
int main(){
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    for(int i = 1; i <= n; i++){
        for(int j = 1;j <= m; j++){
            int t;
            scanf("%d",&t);
            insert(i,j,i,j,t);
        }
    }
    while(q--){
        int x1,y1,x2,y2,t;
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&t);
        insert(x1,y1,x2,y2,t);
    }
    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];
            printf("%d",s[i][j]);
            if(j != m)
                printf(" ");
        }
        if(i!= n)
            printf("\n");
    }
    return 0;
}

双指针算法
最长连续不重复子序列

核心思路:

遍历数组a中的每一个元素a[i], 对于每一个i,找到j使得双指针[j, i]维护的是以a[i]结尾的最长连续不重复子序列,长度为i - j + 1, 将这一长度与r的较大者更新给r。
对于每一个i,如何确定j的位置:由于[j, i - 1]是前一步得到的最长连续不重复子序列,所以如果[j, i]中有重复元素,一定是a[i],因此右移j直到a[i]不重复为止(由于[j, i - 1]已经是前一步的最优解,此时j只可能右移以剔除重复元素a[i],不可能左移增加元素,因此,j具有“单调性”、本题可用双指针降低复杂度)。
用数组s记录子序列a[j ~ i]中各元素出现次数,遍历过程中对于每一个i有四步操作:cin元素a[i] -> 将a[i]出现次数s[a[i]]加1 -> 若a[i]重复则右移j(s[a[j]]要减1) -> 确定j及更新当前长度i - j + 1给r。
注意细节:

当a[i]重复时,先把a[j]次数减1,再右移j。

# include <iostream>
using namespace std;

const int N = 100010;
int a[N], s[N];

int main()
{
    int n, r = 0;
    cin >> n;

    for (int i = 0, j = 0; 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;
}


数组元素的目标和

(双指针) O(n)O(n)
i从 0开始 从前往后遍历
j从 m - 1开始 从后向前遍历

和纯暴力的O(n2)O(n2) 算法的区别就在于

j指针不会回退

#include <iostream>
#include <cstdio>
using namespace std;

const int N = 1e5 + 10;

int n, m, k;
int a[N], b[N];
#define read(x) scanf("%d",&x)

int main()
{
    read(n), read(m), read(k);
    for (int i = 0; i < n; i ++ ) read(a[i]);
    for (int i = 0; i < m; i ++ ) read(b[i]);

    for (int i = 0, j = m - 1; i < n; i ++) {
        while(j >= 0 && a[i] + b[j] > k) j --;
        if(j >= 0 && a[i] + b[j] == k) printf("%d %d\n", i, j);
    }
    return 0;
}


判断子序列
#include<iostream>
#include<cstdio>
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 = 0;i < n; i++) scanf("%d",&a[i]);
    for(int j = 0;j < m; j++) scanf("%d",&b[j]);

    int i = 0;
    for(int j = 0;j < m; j++)
    {
        if(i < n&&a[i] == b[j])  i++;
    }
    if(i == n) puts("Yes");
    else puts("No");
    return 0;
}


位运算
二进制中1的个数

给定一个长度为n的数列,请你求出数列中每个数的二进制表示中1的个数。

#include<iostream>
using namespace std;
int lowbit(int x){
    return x&(-x);
}
int main(){
    int n;
    cin>>n;
    while(n--){
        int x;
        cin>>x;

        int res=0;
        while(x) x-=lowbit(x),res++;

        cout<<res<<' ';
    }
    return 0;
}
离散化
区间和

此处略作分析,为什么要离散化呢,因为存储的下标实在太大了,如果直接开这么大的数组,根本不现实,第二个原因,本文是数轴,要是采用下标的话,可能存在负值,所以也不能,所以有人可能会提出用哈希表,哈希表可以吗?答案也是不可以的,因为哈希表不能像离散化那样缩小数组的空间,导致我们可能需要从-e9遍历到1e9(此处的含义就是假如我们需要计算1e-9和1e9区间内的值,那我们需要从前到后枚举,无论该值是否存在),因为哈希表不能排序,所以我们一般不能提前知道哪些数轴上的点存在哪些不存在,所以一般是从负的最小值到正的最大值都枚举一遍,时间负责度太高,于是就有了本题的离散化。

离散化的本质,是映射,将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。

其实映射最大的难点是前后的映射关系,如何能够将不连续的点映射到连续的数组的下标。此处的解决办法就是开辟额外的数组存放原来的数组下标,或者说下标标志,本文是原来上的数轴上的非连续点的横坐标。
此处的做法是是对原来的数轴下标进行排序,再去重,为什么要去重呢,因为本题提前考虑了前缀和的思想,其实很简单,就是我们需要求出的区间内的和的两端断点不一定有元素,提前加如需要求前缀和的两个端点,有利于我们进行二分搜索,其实二分搜索里面我们一般假定有解的,如果没解的话需要特判,所以提前加入了这些元素,从而导致可能出现重复元素。

本文你用于存储这个关系的数组是alls[N];特地说明下,为什么要开300000+10呢,因为我前面说过了提前考虑了前缀和的因素,加上了2*m个点,又因为怕出现数组越界,多加了10。什么时候会用完300000个空间呢,那就是无重复元素,外加n和m都是1e5次方的打下。

下一步就是写提前数轴点对应的映射后的数组的下标的函数课,此题用的是二分,log(n + 2 * m)

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;
}

为什么返回r + 1,这是变相的让映射后的数组从1开始。此处描述映射后的数组下标对应的数值用的是a数组。

剩下的就是已经讲过的了,前缀后算法,本题的难点是理清楚这个映射关系。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef pair<int, int> PII;
const int N = 300010;
int a[N], s[N];
int n, m;

vector<int> alls;
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) r = mid;
        else l = mid + 1;
    }
    return r + 1;
}
vector<int>:: iterator unique(vector<int> &a)
{
    int j = 0;
    for(int i = 0; i < a.size(); i ++)
        if(!i || a[i] != a[i - 1])
            a[j ++ ] = a[i];
    return a.begin() + j;
}

int main()
{

    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;
        query.push_back({l, r});

        alls.push_back(l);
        alls.push_back(r);
    }

    sort(alls.begin(), alls.end());
    alls.erase(unique(alls), 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;
}


区间合并
区间合并

给定 nn 个区间 [lili,riri],要求合并所有有交集的区间。

注意如果在端点处相交,也算有交集。

输出合并完成后的区间个数。

例如:[11,33]和[22,66]可以合并为一个区间[11,66]。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std ;
typedef pair<int,int> pii ;
vector<pii>nums,res ;
int main()
{
    int st=-2e9,ed=-2e9 ;                           //ed代表区间结尾,st代表区间开头
    int n ;
    scanf("%d",&n) ; 
    while(n--)
    {
        int l,r ; 
        scanf("%d%d",&l,&r) ;
        nums.push_back({l,r}) ;
    }
    sort(nums.begin(),nums.end()) ;                 //按左端点排序
    for(auto num:nums)                   
    {
        if(ed<num.first)                            //情况1:两个区间无法合并
        {
            if(ed!=-2e9) res.push_back({st,ed}) ;   //区间1放进res数组
            st=num.first,ed=num.second ;            //维护区间2
        }
        //情况2:两个区间可以合并,且区间1不包含区间2,区间2不包含区间1
        else if(ed<num.second)  
            ed=num.second ;                         //区间合并
    }  
    //(实际上也有情况3:区间1包含区间2,此时不需要任何操作,可以省略)

    //注:排过序之后,不可能有区间2包含区间1

    if(st!=-2e9&&ed!=-2e9) res.push_back({st,ed});

    //剩下还有一个序列,但循环中没有放进res数组,因为它是序列中的最后一个序列

    /*
    for(auto r:res)
        printf("%d %d\n",r.first,r.second) ;
    puts("") ;
    */

    //(把上面的注释去掉,可以在调试时用)

    printf("%d",res.size()) ;           //输出答案
    return 0 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值