一、排序算法
1.1 快速排序
- 确定分界点x:
q[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[]
- 当
q
[
i
]
≤
x
q[i]\leq x
q[i]≤x时,将
- 分别将
a[]
、b[]
中的数放在q
中
方法2
i
、j
指针分别指向第一个和末尾一个,并分别向右向左移动
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 整数二分算法
检查一个数的范围是否满足某种性质,通过二分算法将一列数分为红色区间(满足性质)和绿色区间(不满足性质),找到边界。
二分并不是找某个数,而是找到符合条件的最小的数或最大的数。
Case1:二分红色区间分界点mid=(l+r+1)/2
,if(check(mid))
://检查mid
是否满足某种性质
- 若为
true
,则mid
取在红色区间内,答案(区间)更新为[mid,r]
,l=mid
; - 若为
false
,则mid
取在绿色区间内,答案(区间)更新为[l,mid-1]
,r=mid-1
。
Case2:二分绿色区间分界点mid=(l+r)/2
,if(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;
}
- 插入元素:
#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} A3A2A1A0−B2B1B0
对每位进行计算: A i − B i − t A_{i}-B_{i}-t Ai−Bi−t,有:
- ≥ 0 \geq 0 ≥0, A i − B i − t A_{i}-B_{i}-t Ai−Bi−t
- < 0 <0 <0,借一位并补上10, A i − B i + 10 − t A_{i}-B_{i}+10-t Ai−Bi+10−t
计算A-B
,有:
- 当
A
≥
B
A\geq B
A≥B时,
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=(3∗12)%10=6
t 1 = ( 3 ∗ 12 ) / 10 = 3 t_{1}=(3*12)/10=3 t1=(3∗12)/10=3
C 1 = ( 2 ∗ 12 + t 1 ) % 10 = 7 C_{1}=(2*12+t_{1})\%10=7 C1=(2∗12+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=(1∗12+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+...+ai(S[0]=0
)
- 求
S
i
S_{i}
Si:
for(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 二维前缀和
容斥原理
-
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]
#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=a2−a1b3=a3−a2...bn=an−an−1
O(n)
时间由B
数组得到A
数组- 给
A
数组区间[l, r]
中的每个数加上c
:B[l] += c
,B[r + 1] -= c
#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
。
#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;
}