基本算法
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;
else
l = 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;
else
r = 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;
else
a[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;
else
l = 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 = midr
else
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 = midr
else
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
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]; // 也可以使用 pair
for (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 1
bool 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;
}
}

浙公网安备 33010602011771号