1-1. 快速排序代码实现:
#include
#include
using namespace std;
const int N = 1e6 + 10;
int q[N];
int n;
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int x = q[l + r >> 1]; // Attention: select the middle number as partition
int 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]);
} // 循环结束后,i和j的取值只有两种情况:i = j 或 i = j + 1
quick_sort(q, l, j); // Attention
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]);
return 0;
}
1-2. 快速选择算法找出第 k 个数
#include
#include
using namespace std;
const int N = 1e5 + 10;
int q[N];
int quick_select(int q[], int l, int r, int k)
{
if (l >= r) return q[l];
int x = q[l + r >> 1];
int 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]);
}
if (k <= j - l + 1) return quick_select(q, l, j, k);
else return quick_select(q, j + 1, r, k - (j - l + 1));
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++) scanf("%d", &q[i]);
cout << quick_select(q, 0, n - 1, k) << endl;
return 0;
}
2-1. 归并排序代码实现:
#include
using namespace std;
const int N = 1e6 + 10;
int n;
int q[N], tmp[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 i = l, j = mid + 1, k = 0;
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];
}
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]);
return 0;
}
2-2. 逆序对的数量
#include
using namespace std;
const int N = 1e5 + 10;
int q[N], tmp[N];
long long findReversedPair(int q[], int l, int r) {
if (l >= r) return 0;
int mid = l + r >> 1;
long long res = findReversedPair(q, l, mid) + findReversedPair(q, mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) {
if (q[i] <= q[j]) tmp[k++] = q[i++];
else {
tmp[k++] = q[j++];
res += mid - i + 1;
}
}
while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];
for (int i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k];
return res;
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &q[i]);
long long res = findReversedPair(q, 0, n - 1);
cout << res << endl;
return 0;
}
#include
using namespace std;
const int N = 1e6 + 10;
int q[N];
int n, m;
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; // look for the left boundary
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; // Attention: l + r + 1
if (q[mid] <= x) l = mid; // look for the right boundary
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
#include
using namespace std;
int main()
{
double x;
cin >> x;
double px = abs(x); // the positive x
double l = 0, r = (px > 1) ? px : 1;
while (r - l > 1e-8)
{
double mid = (l + r) / 2; // Attention: float number cannot use the binary operator '>>'
if (mid * mid * mid > px) r = mid;
else l = mid;
}
if (x < 0) l = -l;
printf("%lf", l);
return 0;
}
【注】本题的数据类型只能用 double
,不能用 float
,因为 float
的精度(即有效数位)只有7位,循环体要求小数点后 8 位,若用 float
,有可能会超时。
用一个数组来存放高精度数,如:
原始数字:123456789
存储:
Rank: 0 1 2 3 4 5 6 7 8
a[i]: 9 8 7 6 5 4 3 2 1
5-1. 高精度加法代码如下:
#include
#include
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B) // Attention: A and B are usually large, so use reference to them to avoid copying
{
vector<int> C; // the result vector
int t = 0; // carry, initialised to 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]; // now t equals to "A[i] + B[i] + t"
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(1);
return C;
}
int main()
{
string a, b; // since the input are large, we use "string" to read them in
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0'); // Attention: '1' - '0' = 1
for (int j = b.size() - 1; j >= 0; j--) B.push_back(b[j] - '0');
auto C = add(A, B);
for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
return 0;
}
5-2. 高精度减法代码:
#include
#include
using namespace std;
bool operator>=(const vector<int> &A, const vector<int> &B)
{
if (A.size() != B.size()) return A.size() > B.size(); // 若位数不一样,则位数多的就大
else // 若位数一样,则
{
for (int i = A.size() - 1; i >= 0; i--) // 从高位往低位开始比较
if (A[i] != B[i]) return A[i] > B[i]; // 看第一位不相等的谁更大
return true;
}
}
vector<int> sub(const vector<int> &A, const 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;
}
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'); // Attention!
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0'); // Attention!
if (A >= B) // 注意:">="运算符已重载
{
auto C = sub(A, B);
for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
}
else
{
auto C = sub(B, A);
cout << '-'; // or " printf("-") ";
for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);
}
return 0;
}
5-3. 高精度乘法:
#include
#include
using namespace std;
vector<int> mul(const vector<int> &A, int b)
{
vector<int> C;
for (int i = 0, t = 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;
}
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;
}
5-4. 高精度除法
#include
#include
#include
using namespace std;
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;
}
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--) printf("%d", C[i]);
cout << endl << r << endl;
return 0;
}
6.前缀和
给定原数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an,则前缀和数组 { S n } \{S_n\} {Sn} 定义为:
S i = a 1 + a 2 + ⋯ + a i (注意:下标从1开始) S_i = a_1 + a_2 + \cdots + a_i \quad\text{(注意:下标从1开始)} Si=a1+a2+⋯+ai(注意:下标从1开始)
特别地,规定 S 0 = 0 S_0 = 0 S0=0。
前缀和数组的递推式: S i = S i − 1 + a i S_i = S_{i-1} + a_i Si=Si−1+ai
前缀和数组的作用:在 O ( 1 ) O(1) O(1) 时间内,对区间 [ l , r ] [l, r] [l,r] 内的数组 a l , ⋯ , a r a_l, \cdots, a_r al,⋯,ar 求和,即:
∑ i = l r a i = S r − S l − 1 \sum \limits _{i=l}^{r} a_i = S_r - S_{l - 1} i=l∑rai=Sr−Sl−1
加速 cin
的读取:
ios::sync_with_stdio(false); // 副作用:关掉这个开关之后,在程序中就不能再混用scanf/printf和cin/cout了
数据输入规模大于等于一百万时,建议用 scanf
,否则建议用 cin
。(scanf
可能会比 cin
快一倍)
6-1. 一维前缀和:
#include
using namespace std;
const int N = 1e5 + 10;
int a[N], S[N];
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++) 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;
}
6-2.二维前缀和(矩阵)
二维前缀和的递推式:
S i , j = S i , j − 1 + S i − 1 , j − S i − 1 , j − 1 + a i , j S_{i, j} = S_{i, j-1} + S_{i-1, j} -S_{i-1, j-1} + a_{i, j} Si,j=Si,j−1+Si−1,j−Si−1,j−1+ai,j
二维前缀和的作用:在 O ( 1 ) O(1) O(1) 时间内,对区域 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)~ ( x 2 , y 2 ) (x_2, y_2) (x2,y2) 内的元素求和:
∑ i = x 1 j = y 1 i = x 2 j = y 2 a i , j = S x 2 , y 2 − S x 2 , y 1 − 1 − S x 1 − 1 , y 2 + S x 1 − 1 , y 1 − 1 \sum \limits _{\substack{i=x_1\\j=y_1}}^{\substack{i=x_2\\j=y_2}} a_{i,j} = S_{x_2, y_2} - S_{x_2, y_1-1} - S_{x_1 - 1, y_2} + S_{x_1 - 1, y_1 -1} i=x1j=y1∑i=x2j=y2ai,j=Sx2,y2−Sx2,y1−1−Sx1−1,y2+Sx1−1,y1−1
二维前缀和代码:
#include
using namespace std;
const int N = 1010;
int a[N][N], 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", &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[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]);
}
return 0;
}
7.差分数组
定义:给定数组 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots, a_n a1,a2,⋯,an,若存在数组 b 1 , b 2 , ⋯ , b n b_1, b_2, \cdots, b_n b1,b2,⋯,bn,使得数组 { a n } \{a_n\} {an} 是数组 { b n } \{b_n\} {bn} 的前缀和,即 ∀ 1 ≤ i ≤ n \forall 1 \le i \le n ∀1≤i≤n,都有 a i = b 1 + b 2 + ⋯ + b i a_i = b_1 + b_2 + \cdots +b_i ai=b1+b2+⋯+bi 成立,则称 { b n } \{b_n\} {bn} 是 { a n } \{a_n\} {an} 的差分数组。
一维差分数组的构造方法:只需令 b 1 = a 1 b_1 = a_1 b1=a1, b 2 = a 2 − a 1 b_2=a_2-a_1 b2=a2−a1, ⋯ \cdots ⋯, b n = a n − a n − 1 b_n=a_n-a_{n-1} bn=an−an−1 即可。
差分数组的作用1:若要对原数组 { a n } \{a_n\} {an} 在某个区间 [ l , r ] [l,r] [l,r] 内的所有元素都加上一个数 c c c,则利用差分数组,只需在 O ( 1 ) O(1) O(1) 时间内即可完成。其具体做法是令 b l ← b l + c b_l \leftarrow b_l + c bl←bl+c 同时令 b r + 1 ← b r + 1 − c b_{r+1} \leftarrow b_{r+1} - c br+1←br+1−c。证明如下:
a l = b 1 + b 2 + ⋯ + b l a l + 1 = b 1 + b 2 + ⋯ + b l + b l + 1 ⋯ a r = b 1 + b 2 + ⋯ + b l + b l + 1 + ⋯ + b r a r + 1 = b 1 + b 2 + ⋯ + b l + b l + 1 + ⋯ + b r + b r + 1 \begin{aligned} & a_l = b_1 + b_2 + \cdots + b_l \\ & a_{l+1} = b_1 + b_2 + \cdots + b_l + b_{l+1} \\ & \cdots \\ & a_r =b_1 + b_2 + \cdots + b_l + b_{l+1}+\cdots + b_r \\ & a_{r+1} =b_1 + b_2 + \cdots + b_l + b_{l+1}+\cdots + b_r + b_{r+1} \\ \end{aligned} al=b1+b2+⋯+blal+1=b1+b2+⋯+bl+bl+1⋯ar=b1+b2+⋯+bl+bl+1+⋯+brar+1=b1+b2+⋯+bl+bl+1+⋯+br+br+1
若让 b l b_l bl 变成 b l + c b_{l}+c bl+c,同时让 b r + 1 b_{r+1} br+1 变成 b r + 1 − c b_{r+1}-c br+1−c,则数组 { a n } \{a_n\} {an} 在区间 [ l , r ] [l,r] [l,r] 内的所有元素都将加 c c c。
差分数组的作用2:若差分数组已知,则只需对差分数组求一遍前缀和,即可在 O ( n ) O(n) O(n) 时间内得到原数组。
一维差分代码:
#include
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N]; // 原数组,差分数组
void insert(int l, int r, int c)
{
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]);
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];
for (int i = 1; i <= n; i++) printf("%d ", b[i]);
return 0;
}
7-2. 二维差分
设 { a i j } \{a_{ij}\} {aij} 是原数组, { b i j } \{b_{ij}\} {bij} 是其差分数组,如果想让区域 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)~ ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 内所有的 a i j a_{ij} aij 都加上一个数 c c c,则需同时进行以下四步操作:
b x 1 , y 1 ← b x 1 , y 1 + c ; / / 该操作会使(x1,y1)右下角的所有元素a都加上c b x 2 + 1 , y 1 ← b x 2 + 1 , y 1 − c ; b x 1 , y 2 + 1 ← b x 1 , y 2 + 1 − c ; b x 2 + 1 , y 2 + 1 ← b x 2 + 1 , y 2 + 1 + c . \begin{aligned} & b_{x_1, y_1} \leftarrow b_{x_1, y_1} + c; //\text{该操作会使(x1,y1)右下角的所有元素a都加上c}\\ & b_{x_2+1, y_1} \leftarrow b_{x_2+1, y_1}-c; \\ & b_{x_1, y_2+1} \leftarrow b_{x_1, y_2+1}-c; \\ & b_{x_2+1, y_2+1} \leftarrow b_{x_2+1, y_2+1} + c. \end{aligned} bx1,y1←bx1,y1+c;//该操作会使(x1,y1)右下角的所有元素a都加上cbx2+1,y1←bx2+1,y1−c;bx1,y2+1←bx1,y2+1−c;bx2+1,y2+1←bx2+1,y2+1+c.
二维差分代码:
#include
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[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= 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++)
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++)
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++)
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]);
puts(""); // puts()函数会自动输出换行符
}
return 0;
}
8-1.最长连续不重复子序列
/* 双指针算法的通用模板 */
for (int i = 0, j = 0; i < n; i++)
{
while (j < i && check(i, j)) j++;
// 每道题目的具体逻辑
}
// 后记:双指针算法的时间复杂度是O(n),因为每个指针至多扫描整个序列一次,所以时间复杂度加起来是O(2n),即O(n)。
最长连续不重复子序列代码:
#include
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], s[N]; // s[N]表示的是区间[j,i]内每个数出现的次数
int main()
{
cin >> n;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
int res = 0;
for (int i = 0, j = 0; i < n; i++)
{
s[a[i]]++;
while (s[a[i]] > 1)
{
s[a[j]]--; j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
8-2. 数组元素的目标和
(从暴力解法优化成双指针解法其实就是就是找单调性。)
#include
using namespace std;
const int N = 1e5 + 10;
int n, m, x;
int a[N], b[N];
int main()
{
cin >> n >> m >> x;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
// 对每一个i,都寻找这样的一个j,使得 a[i] + b[j] >= x,而且j要取最靠左的一个。
// 如此,j便具有了单调性:当i逐渐增大时,j必单调递减。
for (int i = 0, j = m - 1; i < n; i++)
{
while(j >= 0 && a[i] + b[j] > x) j--;
if (j >= 0 && a[i] + b[j] == x) cout << i << ' ' << j << endl;
}
return 0;
}
8-3. 判断子序列:
#include
#include
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
string res = "No";
for (int i = 0, j = 0; i < m; i++)
{
if (a[j] == b[i]) j++;
if (j == n)
{
res = "Yes";
break;
}
}
cout << res << endl;
return 0;
}
区间和:
#include
#include
#include
using namespace std;
typedef pair<int, int> PII;
const int N = 3e5 + 10;
int n, m;
int a[N], s[N];
vector<int> alls;
vector<PII> 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; // 返回r+1是为了求前缀和方便
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int x, c;
cin >> x >> c;
add.push_back({x, c});
alls.push_back(x);
}
for (int i = 0; i < m; i++)
{
int l, r;
cin >> 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());
// 处理插入
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];
// 处理询问
for (auto item : query)
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}