基礎課 第一講 基礎演算法
阿新 • • 發佈:2021-10-27
快速排序
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.逆序對的數量
#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});