AcWing算法----基础算法(1)

198 阅读4分钟

AcWing算法(1)----基础算法

排序

  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

  1. 归并
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
指针排序成有序数组;两两归并数组

二分

  1. 整数二分
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;
}
  1. 浮点数二分
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;
}

前缀和与差分

  1. 前缀和

一维数组 求数组 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];
  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…

  1. 差分矩阵
给以(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的最后一位1lowbit(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;
}