基础算法(一)——补

快排

归并排序和快速排序的时间复杂度都是 O(nlogn)
左右两边设置哨兵,分成左边小于x, 右边大于x。
(先分交换, 再递归)

#include<iostream>
using namespace std;
const int N=1e6+10;
int n;
int q[N];
void quick_sort(int q[], int l , int r)
{
	if(l>=r)
	{
		return;
    }	
    int x=q[l],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()
{
	scanf("%d", &n);
	
	for(int i=0; i<n; i++ )
	{
		scanf("%d",  &q[i]);
	}
	
	quick_sort(q,0,n-1);
	
	for(int i=0; i<n; i++ )
	{
		printf("%d,", q[i]);
	}
	
}

为什么说快速排序一般优于归并排序?
quicksort通常比其他排序算法快得多,因为它在原地运行,而不需要创建任何辅助数组来保存临时值。与merge sort相比,这是一个巨大的优势,因为分配和释放辅助数组所需的时间是显而易见的。就地操作也提高了quicksort的位置。

练习题 https://www.acwing.com/problem/content/788/

快速选择 算法的时间复杂度是 O(n)
只需要递归一边即可,不需要两边都排好,这样时间复杂度就降下来了。
在这里插入图片描述
具体需要的时间就是:
在这里插入图片描述

第k 个 数

在这里插入图片描述
快速选择算法

#include<iostream>
#include<vector> 
using namespace std;
const int N=1e6+10;
int n, k;
int q[N];
int quick_sort(int left, int right,int k)
{
	if( left == right )  // 一定 在 区间 里 面 
	{
		return  q[left];
	}
	int x=q[left], l = left-1, r=right+1; 
	while( l < r  )
	{
		do
		{
			l++;
		}while(q[l]<x);
		do
		{
			r--;
		}while(q[r]>x);
		
		if(l < r)
		{
			swap(q[l],q[r]);
		}
	}
	int sl =  r - left  +  1 ;    // 计算  左  边 一共  有几个 数 字 。 
	if(k  <= sl )     // 在 左半边  
		return quick_sort(left, r,  k); // 注意是 return, 需要返回值的。
	return quick_sort( r+1,right, k-sl  );  // 剩下的右半边  里面是 第 k-sl 个 
}
int main()
{
    cin >> n >> k ;
    for( int i=0 ; i < n ; i++ )
    {
    	cin>>q[i];
	}
    int s=quick_sort(0,n-1,k);
    cout<<s ;
	return 0;
}

归并排序 (分治) 时间复杂度 : nlogn

以数组的中心为分, 分成左边和右边。
① 确定分届点,mid= (left + right)/ 2
② 递归 left,right。
③ 归并—— 合二为一
在两半之中,找较小者。 小的那个 就 拿出来, 最后剩下的 就直接接在后面。
在这里插入图片描述
往后挪就行。 归并排序是稳定的。
在这里插入图片描述
时间复杂度 : nlogn
下面列举可以发现。每一层都是 n, 一共 logn(2为底数)层。
在这里插入图片描述
代码模板

#include<iostream>
using namespace std;
const int N=1e6+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;  // i是 左边界, j 是 右半边的开始位置。 
	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)   //右半边没有结束 ,r是右边界 
	   temp[k++]=q[j++];
    //循环结束,需要把 数据 复制一遍过去。 
	for( i=l, j=0 ; i<=r ; i++, j++ )
	{
		q[i]=temp[j]; 
	} 
}
int main()
{
	scanf("%d", &n);
	for(int i=0; i<n; i++ )
	{
		scanf("%d",  &q[i]);
	}
	
	merge_sort(q , 0 , n-1 );
	
	for(int i=0; i<n; i++ )
	{
		printf("%d,", q[i]);
	}
	
}

归并排序的拓展 —— 求逆序对的数量https://www.acwing.com/problem/content/790/

逆序对: 前面的数 比后面的数大, 则是逆序对。
在这里插入图片描述

二分

整数二分

二分的本质不是单调性。
要符合一些条件。
在这里插入图片描述

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

二分法的实际操作练习
https://www.acwing.com/problem/content/791/
答案

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

浮点数二分

bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

高精度大数加法

在这里插入图片描述

// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++ )
    {
        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;
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高精度大数减法

A-B
这里默认A> B
如果A< B 那么计算-(B-A)
减法
如果本位不够,向后借位。

// C = A - B, 满足A >= B, A >= 0, B >= 0
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];//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;
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

高精度乘低精度 —— 模板题 AcWing 793. 高精度乘法

// C = A * b, A >= 0, b >= 0
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;  //  b 这里 是整个 乘进来。
        C.push_back(t % 10);//尾部添加、
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();// 删除多余的0,//尾端删除元素

    return C;
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

例题案例

#include<iostream>
#include<vector> 
using namespace std;
const int N=1e6+10;
string n;
int  m;

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;  	//  b 这里 是整个 乘进来。
        C.push_back(t % 10);		//尾部添加
        t /= 10;
    }
    while (C.size() > 1 && C.back() == 0) 
		C.pop_back();				// 删除多余的0,//尾端删除元素

    return C;
}
int main()
{
     cin>>n>>m;
	//
   // vector<int>s= mul(n,m);
   vector<int> A;
   string k=n;
   for(int i=n.size()-1; i>=0; i--)
   		A.push_back(n[i]-'0');
   	vector<int> c= mul(A,m);
   	for(int i=c.size()-1; i>=0; i--)
   	    cout<<c[i];

	return 0;
}

高精度除法

// A / b = C ... r, A >= 0, b > 0
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;
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

前缀和

原数组 : A1, A2, A3, A4, A5…AN
前缀和: Si=A1+ A2+ A3+A4+…+Ai
a[l] + … + a[r] = S[r] - S[l - 1]
定义 S【0】=0
这样下标计算就合理了。

练习题 795. 前缀和https://www.acwing.com/problem/content/797/
输入格式

第一行包含两个整数 n和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。

输出格式

共 m 行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,1≤n,m≤100000, −1000≤数列中元素的值≤1000

#include<iostream>
#include<vector> 
using namespace std;
const int N=1e6+10;
int n, m;
int q[N];
int S[N+1];
int main()
{
    cin >> n >> m ;
    S[0]=0;
    for( int i=0 ; i < n ; i++ )
    {
    	cin>>q[i];
    	S[i+1]=S[i]+q[i];
	}
	int l,r  ;
	for(int i = 0 ; i < m ; i ++ )
	{
		cin >> l >> r ;
		cout<<S[r]-S[l-1]<<endl;
	}
	return 0;
}

在这里插入图片描述

二维前缀和

前缀是两个方向的前缀。
在这里插入图片描述
求以(x1, y1) 和(x2, y2 )的正方形的面积
在这里插入图片描述
用面积减法完成
在这里插入图片描述
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]

差分

有数组 a1, a2, a3, … an
构造 数组 b1, b 2, b3, … bn
使得 ai = b1+ b2+ b3+… . + bi (前缀和)

这里 bi 就是 a数组的 差分, 而 a数组 就是 b数组 的前缀和

给区间[l, r]中的每个数加上c:只需要: B[l] += c , B[r + 1] - = c

下面是证明思路:
在这里插入图片描述
给 b[l] 加上c , 则可以发现 al = b1+ b2+ b3+… . + bl 也加上了 C
可以发现 a(l+1) = b1+ b2+ b3+… . + b(l+1) 也很明显加上了 c 。
因为给的区间是 [l, r] 这个区间, 所以到 a(r+1)的位置 都加上了 c 。
这里需要把 b(r+1) -=c 才能让 a(r+1)还是原来的数。

所以这里是差分的思路。

练习

在这里插入图片描述
在这里插入图片描述

直接用公式即可。 不难。

二维差分 —— 模板题 AcWing 798. 差分矩阵

给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

一旦S[x1, y1] += c 之后, 会发现 右下角所有都加上了c,
在这里插入图片描述
把不应该加上c 的位置 ,减去c
在这里插入图片描述
再减去该减的位置
S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c
在这里插入图片描述
有一块多减了一次,所以 加上 S[x2 + 1, y2 + 1] += c
练习 题 https://www.acwing.com/problem/content/800/

双指针算法

位运算

求n的第k位数字: n >> k & 1

lowbit树状数组的一个操作。

lowbit(x)返回x 的最后一个 1
返回n的最后一位1:lowbit(n) = n & -n
在这里插入图片描述

注意计算机中的负数 是用补码来表示的。

因为-x= ~x +1
负x= 取反x +1
lowbit(x)可以判断x中有几个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
在这里插入图片描述

离散化

离散化本质上是一种哈希,它在保持原序列大小关系的前提下把其映射成正整数。
在这里插入图片描述
练习题

区间和
在这里插入图片描述

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
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#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;
}

作者:liangshang
链接:https://www.acwing.com/solution/content/13511/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

区间合并

给定 n个区间 [li,ri],要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。输出合并完成后的区间个数。
例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]
输出: 合并区间完成后的区间个数。

思路:
第一步: 按区间左端点排序
第二步: 扫描整个区间,把可能的交集合并。
维护一个区间。
在这里插入图片描述
第二种情况, 把区间变长,ed往后挪、

在这里插入图片描述

练习题:803. 区间合并 https://www.acwing.com/problem/content/805/

给定 n个区间 [li,ri],要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。输出合并完成后的区间个数。
例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]
输出: 合并区间完成后的区间个数。

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

using namespace std;
const int N=1e6+10;
int n;
int q[N];

typedef pair<int ,int >PII;  // 存一个  数对 
vector<PII> segs;

void merge(vector<PII> &s)
{
	vector <PII> news;
	sort(s.begin(),s.end());  //优先左端点排序。
	int st =-2e9, ed= -2e9;
	for(auto seg : s) 
	{
		if(ed< seg.first)//没有交集,就是需要维护的区间, 更新s, ed  
		{
			if(st!=-2e9)//新区间 
			{
				news.push_back({st,ed}); 
			}
			st=seg.first,ed=seg.second;
		}
		else
		{
			ed=max(ed, seg.second);  //取 大的 那个 更新 即可。 
		}
	}
	if(st != -2e9) news.push_back({st,ed});
	s=news;
}

int main()
{
    cin >> n ;
    int l,r;
    for(int i=0; i<n;i++)
    {
    	cin>>l>>r;
    	segs.push_back({l,r});
	}
    merge(segs);
    cout<<segs.size()<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值