算法基础课1

116 阅读7分钟

y总的算法基础课 acwing的打卡有点不太方便 还是写在这里吧 模板都来自y总

排序

快速排序

分治的思想

  1. 确定分界点 一般是q[l] q[r] q[(r+l)/2] 或者随机
  2. 调整范围 左边全部小于分界点 右边全部大于分界点
  3. 递归处理左右两段

快排也可以用sotr函数
#include<algorithm>
sort(begin, end, cmp)
begin为数组的第一个元素的指针 end为数组的最后一个元素的下一个位置的指针
cmp参数为排序准则 不写的话就是从小到大排序 如果写greater<数据类型>()就是从大到小排序

模板

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[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

例题

/*给定一个长度为 n 的整数数列
所有整数均在 1∼109 范围内。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。1≤n≤100000*/

#include <iostream>

using namespace std;

const int N = 100010;//数组多开10防止越界

int q[N];

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];//dowhile循环所以指针i j要左往右移一个 x为分界点
    while (i < j)//当两个指针相遇时停止循环
    {
        do i ++ ; while (q[i] < x);//i从左往右 大于等于x时停止
        do j -- ; while (q[j] > x);//j从右往左 小于等于x时停止
        if (i < j) swap(q[i], q[j]);//交换
    }

    quick_sort(q, l, j);//递归处理左边的部分
    quick_sort(q, j + 1, r);//递归处理右边的部分
    //如果这里是j x就不能取q[r] 如果是是i x就不能取q[l] 不然会死循环
}

int main()
{
    int n;
    scanf("%d", &n);

    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);//读入 scanf比较快

    quick_sort(q, 0, n-1 );

    for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);//输出

    return 0;
}

归并排序

也是分治的思想

  1. 确定分界点 mid=(l+r)/2
  2. 递归排序左边的和右边
  3. 合二为一 共合并log2 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]) 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];
}

例题

#define _CRT_SECURE_NO_WARNINGS
/*给定你一个长度为 n 的整数数列
所有整数均在 1∼109 范围内。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。1≤n≤100000*/

#include<iostream>

using namespace std;

const int N = 100010;

int q[N], tmp[N];//tmp数组用来存放排好序的变量

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 (int i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];//把tmp数组赋值给q数组
}
int main()
{
	int n;
	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]);

	return 0;
}

二分

整数二分

本质是寻找一个边界
设置一个条件
每次二分后判定是否在符合条件 然后更新范围
有两种模板

模板

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

例题

image.png

#include<iostream>

using namespace std;

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

int main()
{
	int n, q, x;
	scanf("%d %d", &n, &q);
	for (int i = 0; i < n; i++)scanf("%d", &a[i]);

	while (q--)//循环q次
	{
		scanf("%d", &x);//输入要判断的数
		int l = 0, r = n - 1;
		while (l < r)//判断左边界
		{
			int mid = l + r >> 1;
			if (a[mid] >= x) r = mid;//判定条件是>=x 如果满足 就说明a[mid]等于x或在x的右边 范围更新为[l,mid]
			else l = mid + 1;//否则a[mid]在x的左边 范围更新为[mid+1,r]
		}
		if (a[l] != x) printf("-1 -1\n");//若不存在 输出-1 -1
		else
		{
			printf("%d ", l);//输出左边界
			int l = 0, r = n - 1;
			while (l < r)//判断右边界
			{
				int mid = l + r + 1 >> 1;
				if (a[mid] <= x) l = mid; //判定条件是<= x 如果满足 就说明a[mid]等于x或在x的左边 范围更新为[mid,r]
				else r = mid - 1;//否则a[mid]在x的右边 范围更新为[l,mid-1] 这种情况下 mid应该为(l+r+1)/2 在前面补上+1
			}
			printf("%d\n",r);//输出右边界
		}
	}

	return 0;
}

浮点数二分

比整数二分简单得多 没有+1的情况
只需要把误差控制在合理范围内就行 一般是比输出的多两位

模板

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

例题

/*给定一个浮点数 n,求它的三次方根。
−10000≤n≤10000*/

#include<iostream>

using namespace std;

const double N = 1e-8;//误差范围

int main()
{
	double n, mid;
	scanf("%lf", &n);//输入

	double l = -100000.0, r = 100000.0;

	while (r - l > N)//超过误差范围就循环
	{
		mid = (l + r) / 2;
		if (mid * mid * mid >= n) r = mid;
		else l = mid;
	}

	printf("%.6lf", mid);

	return 0;
}

高精度

把输入的大数先存到字符串再一位位倒序存到字符数组中 因为最高位在后面方便变化 运算完成后倒序输出

高精度 + 高精度

模板

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

例题

/*给定两个正整数(不含前导 0),计算它们的和。
1≤整数长度≤100000*/

#include<iostream>
#include<cstring>
#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() || i < 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(1);//最高位位进位

	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);//auto是自动判断变量类型
	for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);//倒序输出数组里的数字

	return 0;
}

高精度 - 高精度

模板

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

例题

/*给定两个正整数(不含前导 0),计算它们的差。
1≤整数长度≤100000*/

#include<iostream>
#include<vector>
#include<string>

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 (B.size() > i)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;

	//把数字逆向读进数组
	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))//判断AB的大小
	{
		auto C = sub(A, B);
		for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
	}
	else//A<B 结果为负数
	{
		printf("-");
		auto C = sub(B, A);
		for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
	}

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

例题

/*给定两个非负整数(不含前导 0) A 和 B,请你计算 A×B 的值。
1≤A的长度≤100000 ,0≤B≤10000*/

#include <iostream>
#include <vector>
#include<string>

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;//不同于竖式一位位乘 这个是直接乘b
        C.push_back(t % 10);//进位
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();//为了防止b=0的情况 去掉前导0

    return C;
}


int main()
{
    string a;
    int b;
    vector<int> A;
    cin >> a >> b;
    
    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--) printf("%d", 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;
}

例题

/*给定两个非负整数(不含前导 0) A,B,请你计算 A/B 的商和余数。
1≤A的长度≤100000 ,
1≤B≤10000
B 一定不为 0*/

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

using namespace std;

// 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];//余数乘10加下一位
        C.push_back(r / b);
        r %= b;//取余
    }
    reverse(C.begin(), C.end());//翻转
    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;//余数
    auto C = div(A, b, r);

    for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);

    printf("\n%d", r);

    return 0;
}

前缀和

一维

有数组a[n] 循环一遍求出s[n] 然后可以快速求出原序列第l个数到第r个数之间的和 即s[r]-s[l-1]
时间复杂度O(n)

模板

S[i] = a[1] + a[2] + ... a[i] = S[i - 1] + a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]  

例题

/*输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000*/

#include<iostream>

using namespace std;

const int N = 100010;

int n, m;
int a[N], s[N];//s[0]=0

int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);//为了防止越界 从a[1]开始

	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]);//区间[l,r]之间的数字和
	}

	return 0;
}

矩阵

模板

S[i, j] = 第i行j列格子左上部分所有元素的和  
S[i, j] += S[i - 1, j] + S[i, j - 1] - S[i - 1, j - 1]
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

例题

image.png

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>

using namespace std;

const int N = 1010;
int s[N][N];

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++) scanf("%d", &s[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];

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

差分

就是前缀和的逆过程

一维

有数组a[i] 构造b[i]使a[i]=b[1]+...+b[i] a称为s的差分 s称为a的前缀和 b[i]=a[i]-a[i-1]
给a数组某区间内插入值的操作如下 给b[i]加上c就可以使a[i]及后面都加c 给b[r+1]减去c就可以使a[r]后面都减c 相当于a[l,r]之间加上了c 其余不变
b数组读入数字可以看成在b[i, i]中插入a[i]

模板

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

例题

image.png

#include<iostream>

using namespace std;

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

void insert(int l, int r, int c)//给a[l,r]之间插入值
{
	b[l] += c;
	b[r + 1] -= c;
}

int main()
{
	int n, m;
	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数组
        //也可以从差分数组的定义出发,for(int i=1;i<=n;i) b[i]=a[i]-a[i-1]; 

	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];//使b数组变成自己的前缀和

	for (int i = 1; i <= n; i++) printf("%d ", b[i]);

	return 0;
}

矩阵

模板

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

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>

using namespace std;

const int N = 1010;
int a[N][N], 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()
{
	int n, m, q;
	scanf("%d %d %d", &n, &m, &q);

    for (int i = 1; i <= n; i++)//读入a数组
        for (int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);

    for (int i = 1; i <= n; i++)//构造b数组
        for (int j = 1; j <= m; j++)
            insert(i, j, i, j, a[i][j]);

    while (q--)//差分操作
    {
        int x1, y1, x2, y2, c;
        scanf("%d %d %d %d %d", &x1, &y1, &x2, &y2, &c);
        insert(x1, y1, x2, y2, c);
    }

    for (int i = 1; i <= n; i++)//使b变成b的前缀和
        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++) printf("%d ", b[i][j]);
        printf("\n");
    }

	return 0;
}