1. 二分与三分
二分
lower_bound 和 upper_bound
在从小到大的排好序的数组中,在数组的 [begin, end)
区间中二分查找第一个大于等于(大于)大于 value
的数,找到返回该数字的地址,没找到则返回 end
lower_bound(begin, end, value); //>=
upper_bound(begin, end, value); //>
拓展:
用 greater<type>() 重载
在从大到小的排好序的数组中,在数组的 [begin, end) 区间中二分查找第一个小于等于(小于)value 的数,找到返回该数字的地址,没找到则返回 end
lower_bound (begin, end, value, greater<int>()) //<=
upper_bound (begin, end, value, greater<int>()) //<
整数二分
左闭右开 (也适用于二分答案)
bool check (int x) { } // 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int lowerBound (int l, int r)
{while (l < r){int mid = l + (r - l >> 1);if (check (mid)) //if (a[mid]>=x)r = mid;elsel = mid + 1;}return l;
}// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int upperBound (int l, int r)
{while (l < r){int mid = l + (r - l + 1 >> 1);if (check (mid))l = mid;elser = mid - 1;}return l;
}
左闭右闭 (仅适用于二分查找)
int binaySearch(vector<int> &a, int k)
{int l = 0, r = a.size() - 1;while (l <= r ){int mid = l + (r - l >> 1);if (a[mid] == k)return mid; elsea[mid] > k ? r = mid - 1 : l = mid + 1; }
}
实数二分
bool check (double x) {}double bsearch_3 (double l, double r)
{const double eps = 1 e-6;while (r - l > eps){double mid = l + (r - l) / 2;if (check (mid))r = mid;elsel = mid;}return l;
}
三分
整数三分
int l, r;
while (r - l > 2)
{ int midl = l + (r - l) / 3, midr = r - (r - l) / 3;if (check (midl) <= check (midr)) l = midl; // 这里是求凸形函数;如果求凹形,那么改为 r = midrelse r = midr;
}
int res = 1e9;
for (int i = l; i <= r; i++) res = min (res, check (i)); // 找到[l, r]区间的范围
实数三分
double l, r;
while (r - l > eps)
{double midl = l + (r - l) / 3, midr = r - (r - l) / 3;if (check (midl) <= check (midr)) l = midl; // 这里是求凸形函数;如果求凹形,那么改为 r = midrelse r = midr;
}
2. 前缀和与差分
一维
a[i] = b[1] + b[2] + ... + b[i]
b[l] + ... + b[r] = a[r] - a[l - 1]//给区间[l, r]中的每个数加上d:
b[l] += d, b[r + 1] -= d
二维
a[i, j] // 第i行j列格子左上部分所有元素的和a[i][j]
= (a[i - 1][j] + a[i][j - 1])
- a[i - 1][j - 1]
+ b[i][j]//以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
a[x2, y2] - a[x1 - 1, y2] - a[x2, y1 - 1] + a[x1 - 1, y1 - 1]//给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上d:
a[x1, y1] += d, a[x2 + 1, y1] -= d, a[x1, y2 + 1] -= d, a[x2 + 1, y2 + 1] += d
三维
a[i, j ,k] // 第i行j列k高度格子左上部分所有元素的和a[i][j][k]
= (a[i - 1][j][k] + a[i][j - 1][k] + a[i][j][k - 1])
- (a[i - 1][j - 1][k] + a[i - 1][j][k - 1] + a[i][j - 1][k - 1])
+ a[i - 1][j - 1][k - 1]
+ b[i][j][k];//以(x1, y1, z1)为左上角,(x2, y2, z2)为右下角的子矩阵的和为:
a[x2][y2][z2]
- a[x1 - 1][y2][z2] - a[x2][y1 - 1][z2] - a[x2][y2][z1 - 1]
+ a[x1 - 1][y1 - 1][z2] + a[x1 - 1][y2][z1 - 1] + a[x2][y1 - 1][z1 - 1]
- a[x1 - 1][y1 - 1][z1 - 1]//给以(x1, y1, z1)为左上角,(x2, y2, z2)为右下角的子矩阵中的所有元素加上d:
a[x1][y1][z1] += d;
a[x1][y2 + 1][z1] -= d;
a[x2 + 1][y1][z1] -= d;
a[x2 + 1][y2 + 1][z1] += d;
a[x1][y1][z2 + 1] -= d;
a[x1][y2 + 1][z2 + 1] += d;
a[x2 + 1][y1][z2 + 1] += d;
a[x2 + 1][y2 + 1][z2 + 1] -= d;
const int N = 110;
ll n, a[N][N][N], q;void solve ()
{ll i, j, k;scanf ("%lld", &n);for (i = 1; i <= n; i++){for (j = 1; j <= n; j++){for (k = 1; k <= n; k++){scanf ("%lld", &a[i][j][k]);a[i][j][k] += a[i][j][k - 1];}}}for (i = 1; i <= n; i++){for (j = 1; j <= n; j++){for (k = 1; k <= n; k++){a[i][j][k] += a[i][j - 1][k];}}}for (i = 1; i <= n; i++){for (j = 1; j <= n; j++){for (k = 1; k <= n; k++){a[i][j][k] += a[i - 1][j][k];}}}scanf ("%lld", &q);while (q--){ll x1, x2, y1, y2, z1, z2;scanf ("%lld%lld%lld%lld%lld%lld", &x1, &x2, &y1, &y2, &z1, &z2);ll ans = a[x2][y2][z2];ans -= a[x1 - 1][y2][z2];ans -= a[x2][y1 - 1][z2];ans -= a[x2][y2][z1 - 1];ans += a[x1 - 1][y1 - 1][z2];ans += a[x1 - 1][y2][z1 - 1];ans += a[x2][y1 - 1][z1 - 1];ans -= a[x1 - 1][y1 - 1][z1 - 1];printf ("%lld\n", ans);}
}
前缀 GCD 与后缀 GCD
problem - 6025 (hdu.edu.cn)
void solve()
{
int n;
cin >> n;
vector<int> a(n + 1), L(n + 1), R(n + 1); // 定义数组 a 和前缀 L、后缀 R for (int i = 1; i <= n; i++) cin >> a[i];// 构造前缀 GCD 数组 L
L[1] = a[1];
for (int i = 2; i <= n; i++)
L[i] = __gcd(L[i - 1], a[i]);// 构造后缀 GCD 数组 R
R[n] = a[n];
for (int i = n - 1; i >= 1; i--)
R[i] = __gcd(R[i + 1], a[i]);// 初始化答案为移除第一个元素或移除最后一个元素时的 GCD
int ans = max(L[n - 1], R[2]);// 遍历中间的元素,计算移除该元素时的 GCD
for (int i = 2; i <= n - 1; i++)
ans = max(ans, __gcd(L[i - 1], R[i + 1])); cout << ans << endl; // 输出最大 GCD
}
3. 高精度
加法
// 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 = 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;
}
乘法
- 高精度乘低精度
// 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;
}
除法
- 高精度除以低精度
// 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;
}
4. 双指针
int i = 0, j = n - 1;
while (i < j)
{... i++;j--;
}for (int i = 0, j = n - 1; i < j; i++, j--)
{}
5. 离散化
模板 1
vector<int> arr; // 存储所有待离散化的值
sort (arr.begin (), arr.end ()); // 将所有值排序
arr.erase (unique (arr.begin (), arr.end ()), arr.end ()); // 去掉重复元素// 二分求出 x 对应的离散化的值
int find (int x) // 找到第一个大于等于 x 的位置
{int l = 0, r = arr.size () - 1;while (l < r){int mid = l + r >> 1;if (arr[mid] >= x) r = mid;else l = mid + 1;}return r + 1; // 映射到 1, 2, ... N
}
模板 2
vector<int> tmp (arr); sort (tmp.begin (), tmp.end ());tmp.erase (unique (tmp.begin (), tmp.end ()), tmp.end ());for (int i = 0; i < n; i++)arr[i] = lower_bound (tmp.begin (), tmp.end (), arr[i]) - tmp.begin ();
模板 3
struct Data
{int idx, val;bool operator<(const Data& o) const {if (val == o.val)return idx < o.idx; // 当值相同时,先出现的元素离散化后的值更小return val < o.val;}
} tmp[maxn]; // 也可以使用 pairfor (int i = 1; i <= n; i++) tmp[i] = (Data){i, arr[i]};sort (tmp + 1, tmp + n + 1);for (int i = 1; i <= n; ++i) arr[tmp[i].idx] = i;
6. 排序与排列
排序
sort
sort (a.begin (), a.end ());//less<int>() //1 2 3 4 5
sort (a.begin (), a.end (), greater<int>()); //5 4 3 2 1bool cmp (...)
{...
}
sort (a.begin (), a.end (), cmp);//lambda表达式
sort(a.begin(), a.end(), [](int x,int y){return x>y;} );
原本应该是这样的:
sort(a.begin(), a.end(), [](int x,int y) -> bool {return x>y;} );
C++可以自动识别函数返回值得数据类型,所以可以简写
struct cri
{int a, b, v;bool operator<(const cri &x) const{return v < x.v;}
} a[N];sort (a + 1, a + n + 1);struct cri
{int a, b, v;bool operator>(const cri &x) const{return v > x.v;}
} a[N];sort (a + 1, a + n + 1, greater<cri>());
冒泡排序
// 假设数组的大小是 n + 1,冒泡排序从数组下标 1 开始
void bubble_sort(int *a, int n)
{bool flag = true;while (flag){flag = false;for (int i = 1; i < n; ++i){if (a[i] > a[i + 1]){flag = true;int t = a[i];a[i] = a[i + 1];a[i + 1] = t;}}}
}
插入排序
- 直接插入排序
void insertion_sort(int arr[], int len)
{for (int i = 1; i < len; ++i){ int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}
}
- 折半插入排序
void insertion_sort(int arr[], int len)
{if (len < 2) return;for (int i = 1; i != len; ++i){int key = arr[i];auto index = upper_bound(arr, arr + i, key) - arr; // 使用 memmove 移动元素,比使用 for 循环速度更快,时间复杂度仍为 O(n) memmove(arr + index + 1, arr + index, (i - index) * sizeof(int));arr[index] = key;}
}
选择排序
#include <utility> void selection_sort(int* a, int n)
{for (int i = 1; i < n; ++i) { int ith = i; for (int j = i + 1; j <= n; ++j) if (a[j] < a[ith]) ith = j;swap(a[i], a[ith]); }
}
快速排序
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);
}
归并排序
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];
}
桶排序
constexpr int N = 100010;
int n, w, a[N];
vector<int> bucket[N];void insertion_sort(vector<int> &A)
{
for (int i = 1; i < A.size(); ++i)
{
int key = A[i];
int j = i - 1;
while (j >= 0 && A[j] > key)
{
A[j + 1] = A[j];
--j;
}
A[j + 1] = key;
}
}void bucket_sort()
{
int bucket_size = w / n + 1;
for (int i = 0; i < n; ++i)
bucket[i].clear();
for (int i = 1; i <= n; ++i)
bucket[a[i] / bucket_size].push_back(a[i]); int p = 0;
for (int i = 0; i < n; ++i)
{
insertion_sort(bucket[i]);
for (int j = 0; j < bucket[i].size(); ++j)
a[++p] = bucket[i][j];
}
}
int a[maxn], bucket[N];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
a[i] = read();
bucket[a[i]]++;
}
for (int i = 0; i <= 1000000; ++i)
{
for (int j = 1; j <= bucket[i]; ++j)
write(i), puts("");
}
}
希尔排序
template <typename T>
void shell_sort(T array[], int length)
{
int h = 1;
while (h < length / 3)
h = 3 * h + 1;
while (h >= 1)
{
for (int i = h; i < length; i++)
{
for (int j = i; j >= h && array[j] < array[j - h]; j -= h)
swap(array[j], array[j - h]);
}
h = h / 3;
}
}
排列
next_permutation 与 prev_permutation
do
{...
}while (next_permutation (v.begin (), v.end ()));
//while (next_permutation (v.begin (), v.end (), cmp));
//获取下一个排列 do
{...
}while (prev_permutation (v.begin (), v.end ()));
//while (prev_permutation (v.begin (), v.end (), cmp));
//获取上一个排列
7. 区间合并
// 将所有存在交集的区间合并
void merge (vector<PII> &segs)
{vector<PII> res;sort (segs.begin (), segs.end ());int st = -2e9, ed = -2e9;for (auto seg : segs)if (ed < seg. first){if (st != -2e9) res.push_back({st, ed});st = seg.first, ed = seg.second;}else ed = max(ed, seg.second);if (st != -2e9) res.push_back({st, ed});segs = res;
}
8. 倍增
利用二进制本身的特性,把一个数 \(N\) 用二进制展开,即
二进制划分反映了一种快速增长的特性,第 \(i\) 位的权值 \(2^i\) 等于前面所有权值的和加 \(1\) ,即
一个整数 \(n\) ,它的二进制只有 \(log_{2}n\) 位。如果要从 \(0\) 增长到 \(n\) ,可以以 \(1,\;2,\;4,\;\cdots,\;2^k\) 为“跳板”,快速跳到 \(n\),这些跳板只有 \(k=log_2n\) 个。
倍增法的局限性是需要提前计算出第 \(1,\;2,\;4,\;\cdots,\;2^k\) 个跳板,这要求数据是静态不变的,不是动态变化的。如果数据发生了变化,那么所有跳板需要重新计算,跳板就失去了意义。
P 4155 [SCOI 2015] 国旗计划 - 洛谷 | 计算机科学教育新生态 (luogu. com. cn)
int n, m;
int n2;
int go[N][20];
int res[N];
struct peo
{
int id, l, r;
bool operator<(const peo a) { return l < a.l; }
} p[N * 2];void init()
{
int nxt = 1;
for (int i = 1; i <= n2; i++)
{
while (nxt <= n2 && p[nxt].l <= p[i].r)
nxt++;
go[i][0] = nxt - 1;
}
for (int i = 1; (1 << i) <= n; i++)
for (int s = 1; s <= n2; s++)
go[s][i] = go[go[s][i - 1]][i - 1];
}void getans(int x)
{
int len = p[x].l + m, cur = x, ans = 1;
for (int i = log2(N); i >= 0; i--)
{
int pos = go[cur][i];
if(pos && p[pos].r < len)
{
ans += 1 << i;
cur = pos;
}
}
res[p[x].id] = ans + 1;
}void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
p[i].id = i;
cin >> p[i].l >> p[i].r;
if (p[i].r < p[i].l)
p[i].r += m;
}
sort(p + 1, p + n + 1);
n2 = n;
for (int i = 1; i <= n; i++)
{
n2++;
p[n2] = p[i];
p[n2].l = p[i].l + m;
p[n2].r = p[i].r + m;
}
init();
for (int i = 1; i <= n;i++)
getans(i);
for (int i = 1; i <= n;i++)
cout << res[i] << ' ';
}
9. 分治
普通分治
将整个问题分解成若干小问题后再分而治之。如果分解得到的子问题相对来说还是太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生方便求解的子问题,必要时逐步合并这些子问题的解,从而得到问题的解。
分治算法可以由递归过程来表示,因为分治法就是一种找大规模问题与小规模问题关系的方法,是递归设计的一种具体策略
汉诺塔
void move(char from , char to)
{cout << "Move" << from << "to" << to <<endl;
}
void Hanoi(int n, char A, char B, char C)
{if(n==1)move(A, C);else{Hanoi(n - 1, A, C, B);move(A, C);Hanoi(n - 1, B, A, C);}
}
int main()
{int n;cin >> n;Hanoi(n,'A','B','C');return 0;
}
根号分治
询问根据一个阈值设为 S
分为两部分。两个部分用不同的算法处理,一部分可以用 暴力直接处理
,另一部分可以通过 预处理和动态维护
。
P 8572 [JRKSJ R 6] Eltaw - 洛谷 | 计算机科学教育新生态 (luogu. com. cn)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int c[705][705];
signed main()
{
int n, k, q;
cin >> n >> k >> q;
vector<vector<int> > a(k + 1, vector<int>(n + 1)); //int a[n + 1][k + 1];
for (int i = 1; i <= k;i++)
for (int j = 1; j <= n;j++)
{
int x;
cin >> x;
a[i][j] = a[i][j - 1] + x;
} if(n<=700)
{
for (int i = 1; i <= k;i++)
for (int j = 1; j <= n;j++)
for (int p = 1; p <= n;p++)
c[j][p] = max(c[j][p], a[i][p] - a[i][j - 1]);
while(q--)
{
int l, r;
cin >> l >> r;
cout << c[l][r] << endl;
} return 0;
} while(q--)
{
int l, r, mx = 0;
cin>>l>>r;
for (int i = 1; i <= k;i++)
mx = max(mx, a[i][r] - a[i][l - 1]); cout << mx << endl;
}
}
10. 贪心法与拟阵
贪心算法及其理论依据——拟阵 - MwingFly - 博客园
贪心算法与拟阵理论-CSDN博客
贪心与拟阵_拟阵与贪心算法-CSDN博客
11.模拟
例题 1
题目描述 : 7-2 奥运排行榜(编程题)-CSDN博客
struct node
{
int id;
double a[4];
} s[N];int t;
int rank_[N][N];void solve()
{
int n, m, k;
cin >> n >> m; for (int i = 0; i < n; i++)
{
cin >> s[i].a[0] >> s[i].a[1] >> k;
s[i].id = i;
s[i].a[2] = s[i].a[0] / k;
s[i].a[3] = s[i].a[1] / k;
} for (t = 0; t < 4; t++)
{
sort(s, s + n, [](node a, node b){ return a.a[t] > b.a[t]; });
rank_[s[0].id][t] = 1;
for (int i = 1; i < n; i++)
{
if (s[i].a[t] == s[i - 1].a[t])
rank_[s[i].id][t] = rank_[s[i - 1].id][t];
else
rank_[s[i].id][t] = i + 1;
}
} int ask;
for (int i = 0; i < m; i++)
{
cin >> ask;
int k = 0;
for (int j = 1; j < 4; j++)
if (rank_[ask][j] < rank_[ask][k])
k = j; if (i != 0)
cout << ' ';
cout << rank_[ask][k] << ":" << k + 1;
}
}