AcWing算法(1)----基础算法
排序
- 快排
void quick_sort(int q[], int l, int r)
{
if (l >= r) return; //判断数组是否大于1
int i = l - 1, j = r + 1, x = q[l + r >> 1];
//由于i,j指针移动再判断,故i,j最初在数组范围外侧 ; x选择取中间值
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);
}
思想:双指针 + 标准值 x(l/r/mid)
解法:有一组数:l------------r
双指针:i->-------<-j
- 归并
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; //指针指向两组数开端
//确定i,j均未超范围
//i,j比较大小,将数组顺序放入tmp中
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 ++ ];
//记得把tmp存储的放回q[l..r]中
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
思想:双指针 + 递归处理(不断的将数组分成两段 进行排序) + 归并(将排好序的有序数组合并)
解法:
有一组数:l---------------r
递归两段区间:l-------------mid & (mid+1)--------------r
l----m&(m+1)----r & l----m&(m+1)----r
递归至子区间为空或者只剩一个元素: x & y,z
指针排序成有序数组;两两归并数组
二分
- 整数二分
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;
}
- 浮点数二分
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;
}
前缀和与差分
- 前缀和
一维数组 求数组 l ~ r 的和:
s = s[r] - s[l - 1]
二维数组 求以(x1,y1)为左上角,(x2,y2)为右下角的矩阵的和:
s = s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1];
- 差分(可以看成前缀和的逆运算)
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
首先给定一个原数组a:a[1], a[2], a[3],,,,,, a[n];
然后我们构造一个数组b : b[1] ,b[2] , b[3],,,,,, b[i];
使得 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i]
即:
a[0 ]= 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
........
b[n] = a[n] - a[n-1];
给定区间[l ,r ],让我们把a数组中的[ l, r]区间中的每一个数都加上c,即 a[l] + c , a[l+1] + c , a[l+2] + c ,,,,,, a[r] + c;
暴力做法是for循环l到r区间,时间复杂度O(n),如果我们需要对原数组执行m次这样的操作,时间复杂度就会变成O(n*m)。有没有更高效的做法吗? 考虑差分做法。
始终要记得,a数组是b数组的前缀和数组,比如对b数组的b[i]的修改,会影响到a数组中从a[i]及往后的每一个数。
首先让差分b数组中的 b[l] + c ,a数组变成 a[l] + c ,a[l+1] + c,,,,,, a[n] + c;
然后我们打个补丁,b[r+1] - c, a数组变成 a[r+1] - c,a[r+2] - c,,,,,,,a[n] - c;
为啥还要打个补丁?
我们画个图理解一下这个公式的由来:
b[l] + c,效果使得a数组中 a[l]及以后的数都加上了c(红色部分),但我们只要求l到r区间加上c, 因此还需要执行 b[r+1] - c,让a数组中a[r+1]及往后的区间再减去c(绿色部分),这样对于a[r] 以后区间的数相当于没有发生改变。
因此我们得出一维差分结论:给a数组中的[ l, r]区间中的每一个数都加上c,只需对差分数组b做 b[l] + = c, b[r+1] - = c。时间复杂度为O(1), 大大提高了效率。
总结:
©差分作者:林小鹿;链接:www.acwing.com/solution/co…
- 差分矩阵
给以(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
双指针算法
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
位运算
求x的第k位数字: x >> k & 1
返回x的最后一位1:lowbit(x) = x & -x
lowbit(x): x & (~x)
x= 1010...1000
~x= 0101...0111
~x+1= 0101...1000
x&(~x+1)=0000...1000
PS: x的二进制表示中表示第二位是几
x = 15 = (11111) <第43210位>
离散化
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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; // 映射到1, 2, ...n
}
区间合并
// 将所有存在交集的区间合并
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;
}