y总的算法基础课 acwing的打卡有点不太方便 还是写在这里吧 模板都来自y总
排序
快速排序
分治的思想
- 确定分界点 一般是q[l] q[r] q[(r+l)/2] 或者随机
- 调整范围 左边全部小于分界点 右边全部大于分界点
- 递归处理左右两段
快排也可以用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;
}
归并排序
也是分治的思想
- 确定分界点 mid=(l+r)/2
- 递归排序左边的和右边
- 合二为一 共合并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;
}
例题
#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))//判断A和B的大小
{
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]
例题
#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
例题
#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
例题
#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;
}