1. 程式人生 > 其它 >基礎課 第一講 基礎演算法

基礎課 第一講 基礎演算法

快速排序

785.快速排序

排序看似簡單,其實邊界問題還挺麻煩

786.第k個數(快速選擇\(O(n)\)

求陣列中第k大的數

快速選擇演算法——只用遞迴一邊的快排,複雜度 \(O(n)\)

在快速排序的某次遞迴中,記左區間有 \(L\) 個元素,右區間有 \(R\) 個元素。如果 \(k\le L\) 則遞迴左區間,找左區間中第 \(k\) 大的數。否則遞迴右區間,找右區間中第 \(k-L\) 大的數。

#include <bits/stdc++.h>
using namespace std;
const signed N = 1e5+10;

int a[N];

int qs(int q[], int l, int r, int k)
{
    if (l == r) return q[l];
    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]);
    }
    int L = j-l+1;
    return k <= L ? qs(q,l,j,k) : qs(q,j+1,r,k-L);
}

signed main()
{
    int n, k; scanf("%d%d", &n, &k);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    printf("%d", qs(a, 0, n - 1, k));

    return 0;
}

歸併排序

787.歸併排序
788.逆序對的數量

luogu P1908 逆序對

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const signed N = 5e5+10;

int a[N];

int tmp[N];
ll ms(int l, int r)
{
    if(l >= r) return 0;
    int mid = l + r >> 1;
    ll res = ms(l, mid) + ms(mid + 1, r); //核心

    int k = 0, i = l, j = mid + 1;
    while(i <= mid && j <= r)
        if (a[i] <= a[j]) tmp[k++] = a[i++];
        else tmp[k++] = a[j++], res += mid - i + 1; //核心
    while(i <= mid) tmp[k++] = a[i++];
    while(j <= r) tmp[k++] = a[j++];
    for(i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];

    return res;
}

signed main()
{
    int n; scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    printf("%lld", ms(0, n-1));

    return 0;
}

二分

789.數的範圍

注意找不到的話 \(l==\) 區間右端點

790.數的三次方根

高精度

四題都鎖了,不想寫了

791.高精度加法
792.高精度減法
793.高精度乘法
794.高精度除法

字首和與差分

795.字首和
796.子矩陣的和

二維字首和 S[i][j] = a[i][j] + S[i-1][j] + S[i][j-1] - S[i-1][j-1]

797.差分

可以把輸入 a[i] 視為把區間 \([i,i]\) 中的數加上 a[i] ,從而用區間加建立差分陣列

#include <bits/stdc++.h>
using namespace std;
const signed N = 1e5+10;
int b[N];
void ins(int l, int r, int x) {b[l] += x; b[r+1] -= x; }
signed main()
{
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int tmp; cin >> tmp;
        ins(i,i,tmp);
    }
    while(m--)
    {
        int l ,r, x; cin >> l >> r >> x;
        ins(l,r,x);
    }
    for(int i = 1; i <= n; i++)
    {
        b[i] += b[i - 1]; //求字首和得到正常矩陣
        cout << b[i] << ' ';
    }

    return 0;
}

798.差分矩陣

原矩陣是差分矩陣的字首和,某點加 c 會把這個點右下的數全加 c

對矩形 (x1,y1)(x2,y2) 中的元素加 c:

b[x1][y1]+=c, b[x2+1][y1]-=c, b[x1][y2+1]-=c, b[x2+1][y2+1]+=c

由差分矩陣得到正常矩陣:

b[i][j] += b[i-1][j] + b[i][j - 1] - b[i - 1][j - 1]

雙指標演算法

“單調關係”

799.最長連續不重複子序列
//j在i的左邊,注意在i右移的同時,j也不能左移,是為“單調關係”
for(int i = 0, j = 0; i < n; i++)
{
    cnt[a[i]]++; //字元出現次數
    while(s[a[i]] > 1) s[a[j]--], j++;
    ans = max(ans, i - j + 1);
}
800.陣列元素的目標和

輸出滿足 a[i] + b[j] == x 的所有 a[i]b[j] 。如果解唯一就能用雙指標,否則不行,要用雜湊(怎麼做呢)

位運算

801.二進位制中1的個數
while(x) x -= lowbit(x), ans++;

離散化

802.區間和

假定有一個無限長的數軸,數軸上每個座標上的數都是0。

現在,我們首先進行 n 次操作,每次操作將某一位置x上的數加c。

接下來,進行 m 次詢問,每個詢問包含兩個整數l和r,你需要求出在區間[l, r]之間的所有數的和。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N];
vector<int> alls;
vector<PII> add, query;
int find(int x){ //此函式可用lower_bound代替
    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開始方便字首和
}
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;
}

區間合併

803.區間合併

把有交集或端點相交的區間合併,輸出合併後區間數

按左端點排序,逐個判斷前後區間是否相交

sort(segs.begin(), segs.end());
int st = -2e9, ed = 2e9;
for(auto seg : segs)
    if(ed < seg.first)
    {
        if(st != 2e9) ans.push_back({st, ed});
        st = seg.first, ed = seg.second;
    }
    else ed = max(ed, seg.second);
if(st != 2e9) ans.push_back({st, ed});