[Acwing算法基础课]1.基础算法(一)笔记

本文详细介绍了排序算法中的快速排序和归并排序,以及搜索算法中的二分查找。在快速排序中,通过选取分界点并调整区间实现递归排序;归并排序则采用分治策略,将数组分成两半分别排序后再合并。二分查找用于在有序数组中寻找特定元素,通过不断缩小查找范围直至找到目标。此外,文章还探讨了高精度计算,包括大整数的存储、加法、减法、乘法和除法操作。这些基础知识对于理解和解决算法问题至关重要。
摘要由CSDN通过智能技术生成

一、排序算法

1.1 快速排序

  • 确定分界点xq[1]q[(1+r)/2]q[r]、随机
  • 调整区间:保证所有小于等于x的数在x的左区间,大于等于x的数在x的右区间
  • 递归处理左右两段

调整区间可实现的方法:

方法1

  • 开额外的数组a[]b[]
  • 扫描q[1-r]
    • q [ i ] ≤ x q[i]\leq x q[i]x时,将x插入到a[]
    • q [ i ] ≥ x q[i]\geq x q[i]x时,将x插入到b[]
  • 分别将a[]b[]中的数放在q

方法2

ij指针分别指向第一个和末尾一个,并分别向右向左移动

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

1.2 归并排序

采用分治的思想。

  • 确定分界点:mid=(1+r)/2
  • 递归排序left、right
  • 归并,合二为一
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]) 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];
}

二、二分算法

2.1 整数二分算法

检查一个数的范围是否满足某种性质,通过二分算法将一列数分为红色区间(满足性质)和绿色区间(不满足性质),找到边界

二分并不是找某个数,而是找到符合条件的最小的数或最大的数。

image-20220720214820406

Case1:二分红色区间分界点mid=(l+r+1)/2if(check(mid))://检查mid是否满足某种性质

  • 若为true,则mid取在红色区间内,答案(区间)更新为[mid,r]l=mid
  • 若为false,则mid取在绿色区间内,答案(区间)更新为[l,mid-1]r=mid-1

Case2:二分绿色区间分界点mid=(l+r)/2if(check(mid))

  • 若为true,则mid取在红色区间内,答案(区间)更新为[1,mid]r=mid
  • 若为false,则mid取在绿色区间内,答案(区间)更新为[mid+1,r]l=mid+1
#include<iostream>
using namespace std;
const int N = 100100;
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) printf("-1 -1\n");
        else
        {
            printf("%d ",l);
            //左边都满足<=x,右边不满足
            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;
            }
            printf("%d\n",l);
        }
    }
}

2.2 浮点数二分算法

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;
}
#include<iostream>
using namespace std;
int main()
{
    double x;
    cin >> x;
    double l = -10000,r = 10000;
    while(r - l > 1e-8)
    {
        double mid = (l + r) /2;
        if(mid * mid * mid>= x)
        {
            r = mid;
        }
        else
        {
            l = mid;
        }
    }
    printf("%lf\n",l);
    return 0;
}

三、高精度

大整数通过数组存储,数组每一位存一位数字,小端形式

3.1 高精度加法

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

【知识点】C++ vector

vector是一个可以不用再初始化就必须制定大小的变长数组。

  • 初始化
vector<int> v1;
vector<father> v2;
vector<string> v3;
vector<vector<int> >;  //注意空格。这里相当于二维数组int a[n][n];
vector<int> v5 = { 1,2,3,4,5 }; //列表初始化,注意使用的是花括号
vector<string> v6 = { "hi","my","name","is","lee" };
vector<int> v7(5, -1); //初始化为-1,-1,-1,-1,-1。第一个参数是数目,第二个参数是要初始化的值
vector<string> v8(3, "hi");
vector<int> v9(10); //默认初始化为0
vector<int> v10(4); //默认初始化为空字符串
  • 添加元素

通过**push_back()**添加元素,该元素添加到vector数组的末尾。

  • 访问vector中的元素
for (int i = 0; i < v1.size(); i++)
{
	v1[i] = 100;
	cout << v1[i] << endl;
}

使用迭代器来访问元素:

vector<string> v6 = { "hi","my","name","is","lee" };
for (vector<string>::iterator iter = v6.begin(); iter != v6.end(); iter++)
{
	cout << *iter << endl;
	//下面两种方法都都可以检查迭代器是否为空
	cout << (*iter).empty() << endl;
	cout << iter->empty() << endl; 
}
  • 插入元素

img

#include <iostream> 
#include <vector> 
#include <array> 
using namespace std;
int main()
{
    std::vector<int> demo{1,2};
    //第一种格式用法
    demo.insert(demo.begin() + 1, 3);//{1,3,2}
 
    //第二种格式用法
    demo.insert(demo.end(), 2, 5);//{1,3,2,5,5}
 
    //第三种格式用法
    std::array<int,3>test{ 7,8,9 };
    demo.insert(demo.end(), test.begin(), test.end());//{1,3,2,5,5,7,8,9}
 
    //第四种格式用法
    demo.insert(demo.end(), { 10,11 });//{1,3,2,5,5,7,8,9,10,11}
 
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    return 0;
}
  • 删除元素

通过pop_back() 方法删除元素。该方法删除最后一个元素,若需删除里面的一个元素,可以先交换其与最后一个元素的位置,再进行元素删除。

#include <iostream>
#include <vector>
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);//为了方便计算,让A中保存较长的数字, B中保存较短的数字

    vector<int> C;//保存结果的数组
    int t = 0;//进位,开始时是0
    for (int i = 0; i < A.size(); i ++ )//依次计算每一位
    {
        t += A[i];//加上 A 的第 i 位上的数字
        if (i < B.size()) t += B[i];//加上 B 的第 i 位上的数字
        C.push_back(t % 10); //C 中放入结果
        t /= 10;//t 更新成进位
    }

    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];//倒序输出C中的数字
    cout << endl;
    return 0;
}

3.2 高精度减法

A 3 A 2 A 1 A 0 − B 2 B 1 B 0 A_{3}A_{2}A_{1}A_{0}-B_{2}B_{1}B_{0} A3A2A1A0B2B1B0

对每位进行计算: A i − B i − t A_{i}-B_{i}-t AiBit,有:

  • ≥ 0 \geq 0 0 A i − B i − t A_{i}-B_{i}-t AiBit
  • < 0 <0 <0,借一位并补上10, A i − B i + 10 − t A_{i}-B_{i}+10-t AiBi+10t

计算A-B,有:

  • A ≥ B A\geq B AB时,A-B
  • 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];
        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;
}
#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;
}
//C = A - B
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();	//去除前导0
	return C;
}
int main()
{
	string a, b;
	vector<int> A, B;
	cin >> a >> b;	//a = "123456"
	for (int i = a.size() - 1; i >= 0; i--)
	{
		A.push_back(a[i] - '0'); //A = [6,5,4,3,2,1]
	}
	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--)
		{
			cout << C[i];
		}
	}
	else {
		auto C = sub(B, A);
		cout << "-";
		for (int i = C.size() - 1; i >= 0; i--)
		{
			cout << C[i];
		}
	}
	return 0;
}

3.3 高精度乘低精度

手算模拟:每个被乘数位与B的整体进行相乘

A 1 2 3

B 1 2


C 3 C 2 C 1 C 0 C_{3}C_{2}C_{1}C_{0} C3C2C1C0

每位分别是:

C 0 = ( 3 ∗ 12 ) % 10 = 6 C_{0}=(3*12)\%10=6 C0=(312)%10=6

t 1 = ( 3 ∗ 12 ) / 10 = 3 t_{1}=(3*12)/10=3 t1=(312)/10=3

C 1 = ( 2 ∗ 12 + t 1 ) % 10 = 7 C_{1}=(2*12+t_{1})\%10=7 C1=(212+t1)%10=7

t 2 = 2 t_{2}=2 t2=2

C 2 = ( 1 ∗ 12 + t 2 ) % 10 = 4 C_{2}=(1*12+t_{2})\%10=4 C2=(112+t2)%10=4

t 3 = 1 t_{3}=1 t3=1

C 3 = 1 C_{3}=1 C3=1

// 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;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}
#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();	//去除前导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');
	auto C = mul(A, b);
	for (int i = C.size() - 1; i >= 0; i--)
		cout << C[i];
	return 0;
}

3.4 高精度除以低精度

// 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;
}
#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());	//algorithm头文件中的函数
	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');
	}
	int r;
	auto C = div(A, b, r);
	for (int i = C.size() - 1; i >= 0; i--)
	{
		cout << C[i];
	}
	cout << endl << r << endl;
	return 0;
}

四、前缀和

4.1 一维前缀和

前缀和表示为: S i = a 1 + a 2 + . . . + a i S_{i}=a_{1}+a_{2}+...+a_{i} Si=a1+a2+...+aiS[0]=0

  • S i S_{i} Sifor(i = 1; i <= n; i++) {S[i] = S[i-1] + a[i];}
  • 作用:范围为[l,r]数组的和,若没有前缀和数组,时间复杂度为O(n);运用前缀和数组,可以用S[r]-S[l-1]进行计算,时间复杂度为O(1)
#include<iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N];
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
	    scanf("%d",&a[i]); 
	}
	for (int i = 1; i <= n; i++)
	{
		s[i] = s[i - 1] + a[i];		//前缀和的初始化
	}
	while (m--)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		printf("%d\n", s[r] - s[l - 1]);	//区间和的计算
	}
	return 0;
}

4.2 二维前缀和

容斥原理

image-20220722160459614

  • S[i, j] = 第ij列格子左上部分所有元素的和

  • (x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:

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

#include<iostream>
const int N = 1010;

int n, m, q;
int a[N][N], s[N][N];

int main()
{
	scanf("%d%d%d", &n, &m, &q);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			scanf("%d", &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, y1, x2, 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;
}

五、差分

5.1 一维差分

a 1 , a 2 , . . . , a n a_{1},a_{2},...,a_{n} a1,a2,...,an[前缀和],构造 b 1 , b 2 , . . . , b n b_{1},b_{2},...,b_{n} b1,b2,...,bn[差分],使得 a i = b 1 + b 2 + . . . + b i a_{i}=b_{1}+b_{2}+...+b_{i} ai=b1+b2+...+bi,则有:

{ b 1 = a 1 b 2 = a 2 − a 1 b 3 = a 3 − a 2 . . . b n = a n − a n − 1 \begin{cases} b_{1}=a_{1} \\ b_{2}= a_{2}-a_{1} \\ b_{3}= a_{3}-a_{2} \\ ... \\ b_{n}= a_{n}-a_{n-1} \end{cases} b1=a1b2=a2a1b3=a3a2...bn=anan1

  • O(n)时间由B数组得到A数组
  • A数组区间[l, r]中的每个数加上cB[l] += c, B[r + 1] -= c

image-20220722162509157

#include<iostream>
using namespace std;

const int N = 100010;
int n, m;
int a[N], b[N];

void insert(int l, int r, int c)
{
	b[l] += c;
	b[r + 1] -= c;
}

int main()
{
	scanf("%d%d", &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]);			//将b数组读入值
	}
	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];			//前缀和得到"新的a[i]"
	for (int i = 1; i <= n; i++)
		printf("%d ", b[i]);
	return 0;
}

5.2 二维差分

a[i][j]b[i][j]的前缀和。

给以(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

image-20220722171706570

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N];

// 插入到差分矩阵中
void insert(int x1, int y1, int x2, int y2, int c) {
    a[x1][y1] += c;
    a[x2 + 1][y1] -= c;
    a[x1][y2 + 1] -= c;
    a[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++) {
            int c;
            cin >> c;
            insert(i, j, i, j, c);		//读取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++) {
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
            cout << a[i][j] << " ";
        }
        puts("");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值