算法阶段总结1

阶段总结

通过今天晚上的这场div2我深刻的意识到,光是会找窍门是远远不够的,你得会基础的建图,dp,高级数据结构,你这样才可以不断的提升自己,不可以一直在一个阶段停留下去,构造题可以刷下去,但是要开始复习之前的东西和新算法的学习了。为此这篇总结,我将以我现在的代码风格写出算法基础课的所有算法模板。

1.快速排序

c++ 复制代码
// Problem: 快速排序
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/787/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Author:lvzihao521
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
using pii = pair<i64, i64>;

void solve() {
	int n;
	cin >> n;
	vector<int> a(n);
	for (int i = 0; i < n; i ++) {
		cin >> a[i];
	}
	function<void(vector<int> &a, int l, int r)> quick_sort = [&](vector<int> &a, int l, int r) -> void {
		if (l >= r) {
			return ;
		}
		int i = l - 1, j = r + 1, x = a[l + r >> 1];
		while (i < j) {
			do {
				i ++;
			} while (a[i] < x);
			do {
				j --;
			} while (a[j] > x);
			if (i < j) {
				swap(a[i], a[j]);
			}
		}
		quick_sort(a, l, j);
		quick_sort(a, j + 1, r);
	};
	quick_sort(a, 0, n - 1);
	for (int i = 0; i < n; i ++) {
		cout << a[i] << " \n"[i == n - 1];
	}
}

int main() {
  int t;
  t = 1;
  while (t --) {
    solve();
  }
  return 0;
}
第k个数
// Problem: 第k个数
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/788/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Author:lvzihao521
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
using pii = pair<i64, i64>;

void solve() {
   int n, k;
   cin >> n >> k;
   vector<int> a(n);
   for (int i = 0; i < n; i ++) {
   	cin >> a[i];
   }
   function<int(int l, int r, int k)> quick_sort = [&](int l, int r, int k) -> int {
   	if (l >= r) {
   		return a[l];
   	}
   	int i = l - 1, j = r + 1, x = a[l + r >> 1];
   	while (i < j) {
   		do {
   			i ++;
   		} while (a[i] < x);
   		do {
   			j --;
   		} while (a[j] > x);
   		if (i < j) {
   			swap(a[i], a[j]);
   		}
   	}
   	if (j - l + 1 >= k) {
   		return quick_sort(l, j, k);
   	} else {
   		return quick_sort(j + 1, r, k - (j - l + 1));
   	}
   };
   cout << quick_sort(0, n - 1, k) << endl;
}

int main() {
 int t;
 t = 1;
 while (t --) {
   solve();
 }
 return 0;
}

2.归并排序

// Problem: 归并排序
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/789/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Author:lvzihao521
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
using pii = pair<i64, i64>;

void solve() {
	int n;
	cin >> n;
	vector<int> a(n), t(n);
	for (int i = 0; i < n; i ++) {
		cin >> a[i];
	}
	function<void(int l, int r)> merge_sort = [&](int l, int r) -> void {
		if (l >= r) {
			return ;
		}
		int mid = l + r >> 1;
		merge_sort(l, mid);
		merge_sort(mid + 1, r);
		int i = l, j = mid + 1, k = 0;
		while (i <= mid && j <= r) {
			if (a[i] <= a[j]) {
				t[k ++] = a[i ++];
			} else {
				t[k ++] = a[j ++];
			}
		}
		while (i <= mid) {
			t[k ++] = a[i ++];
		}
		while (j <= r) {
			t[k ++] = a[j ++];
		}
		for (i = l, k = 0; i <= r; i ++, k ++) {
			a[i] = t[k];
		}
	};
	merge_sort(0, n - 1);
	for (int i = 0; i < n; i ++) {
		cout << a[i] << " \n"[i == n - 1];
	}
}

int main() {
  int t;
  t = 1;
  while (t --) {
    solve();
  }
  return 0;
}
逆序对个数
// Problem: 逆序对的数量
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/790/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Author:lvzihao521
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
using pii = pair<i64, i64>;

void solve() {
	int n;
	cin >> n;
	vector<int> a(n), t(n);
	for (int i = 0; i < n; i ++) {
		cin >> a[i];
	}
	function<i64(int l, int r)> merge_sort = [&](int l, int r) -> i64 {
		i64 res = 0;
		if (l >= r) {
			return 0;
		}
		int mid = l + r >> 1;
		res += merge_sort(l, mid) + merge_sort(mid + 1, r);
		int i = l, j = mid + 1, k = 0;
		while (i <= mid && j <= r) {
			if (a[i] <= a[j]) {
				t[k ++] = a[i ++];
			} else {
				t[k ++] = a[j ++];
				res += mid - i + 1;
			}
		}
		while (i <= mid) {
			t[k ++] = a[i ++];
		}
		while (j <= r) {
			t[k ++] = a[j ++];
		}
		for (i = l, k = 0; i <= r; i ++, k ++) {
			a[i] = t[k];
		}
		return res;
	};
	i64 res = merge_sort(0, n - 1);
	cout << res << endl;
}

int main() {
  int t;
  t = 1;
  while (t --) {
    solve();
  }
  return 0;
}

3.二分

二分不是特别简单的题面都是需要写一个check函数的,check函数是二分的一个难点,因题而异,所以我下面的模板是基于check函数写好以后写的

区间左端点

int l = 0, r = 1e9;
while (l < r) {
    int mid = l + r >> 1;
    if (check(mid)) r = mid;
    else l = mid + 1;
}

区间右端点

int l = 0, r = 1e9;
while (l < r) {
    int mid = l + r + 1 >> 1;
    if (check(mid)) l = mid;
    else r = mid - 1;
}

4.高精度

高精度顾名思义就是一些数字太大了,比如有1000位,无法直接进行运算,但是我们可以通过把这些数字放到数组里,然后一位一位的做运算,相当于在模拟咱们手动列竖式的感觉

1.加法

vector<int> add(vector<int> &a, vector<int> &b) {
	vector<int> res;
	int t = 0;
	for (int i = 0; i < a.size() || i < b.size(); i ++) {
        if (i < a.size()) {
            t += a[i];
        }
        if (i < b.size()) {
            t += b[i];
        }
        res.emplace_back(t % 10);
        t /= 10;
	}
	if (t) {
        res.emplace_back(t);
	}
	return res;
}

2.减法

减法的话,他需要多写一个比较函数,因为咱们只能实现大数减小数,如果是小数减大数的话,通过cmp函数改成大数减小数,并且手动加负号就可以,我在这里只是实现一下这两个函数,

在做完运算后,可能会出现123456 - 123000 = 000456的问题,所以最后我们要去除前导零。

bool cmp(vector<int> &a, vector<int> &b) {
    if (a.size() != b.size()) {
        return a.size() > b.size();
    }
    for (int i = a.size() - 1; i >= 0; i --) {
    	if (a[i] != b[i]) {
            return a[i] > b[i];
    	}
    }
    return true;
}



vector<int> sub(vector<int> &a, vector<int> &b) {
    vector<int> res;
    int t = 0;
    for (int i = 0; i < a.size(); i ++) {
        t = a[i] - t;
        if (i < b.size()) {
            t -= b[i];
        }
        res.emplace_back((t + 10) % 10);
        t = t < 0 ? 1 : 0;
    }
    while (res.size() > 1 && res.back() == 0) {
        res.pop_back();
    }
    return res;
}

3.乘法

乘法的模板我分为两个,一个高精度乘低精度,一个高精度乘高精度,前一个更常用,要比后一个要熟练掌握

vector<int> mul(vector<int> &a, int b) {
	vector<int> res;
	int t = 0;
	for (int i = 0; i < a.size() || t; i ++) {
		if (i < a.size()) {
            t += a[i] * b;
		}
		res.emplace_back(t % 10);
		t /= 10;
	}
	while (res.size() > 1 && res.back() == 0) {
        res.pop_back();
	}
	return res;
}

vector<int> mul(vector<int> &a, vector<int> &b) {
    vector<int> res(a.size() + b.size() + 10);
    for (int i = 0; i < a.size(); i ++) {
        for (int j = 0; j < b.size(); j ++) {
            res[i + j] += a[i] * b[j];
        }
    }
    int t = 0;
    //完成进位的操作
    for (int i = 0; i < res.size(); i ++) {
        t += res[i];
        res[i] = t % 10;
        t /= 10;
    }
    while (res.size() > 1 && res.back() == 0) {
        res.pop_back();
    }
    return res;
}

4.除法

由于本人不熟悉除法(我根本没有用过),所以这里我也是复制粘贴来的。原理也不难,可以求得商和余数,一样类比列竖式。

vector<int> div(vector<int> &A, int b, int &r) {
    vector<int> res;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        res.emplace_back(r / b);
        r %= b;
    }
    reverse(res.begin(), res.end());
    while (res.size() > 1 && res.back() == 0){
    	res.pop_back();
    }
    return res;
}

5.前缀和

5.1一维前缀和

这就是给咱们一个数组,然后对他进行一个操作,使得下标为i就是前i项和,这是O(1)的复杂度

vector<int> s(n + 1);
//....
//经过赋值后
for (int i = 1; i <= n; i ++) {
    s[i] += s[i - 1];
}
//不想改变原数组的话,也可以这样
//vector<int> b(n + 1);
//for (int i = 0; i < n; i ++) {
//    b[i] = b[i - 1] + s[i];
//}

5.2二维前缀和

和上面同理s[i][j]就表示长为j,高为i的矩阵的和

vector<vector<int>> s(n + 10, vector<int> (m + 10));
//...
//经过赋值后
for (int i = 1; i <= n; i ++) {
    for (int j = 1; j <= m; j ++) {
        s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    }
}

6.差分

这可以看成是前缀和的逆运算

它可以做到对一个区间内用一次操作来加减一个数

也有题的类型是找最小操作数,也是要用差分

6.1一维差分

vector<int> s(n + 1);
for (int i = n; i >= 1; i --) {
    s[i] -= s[i - 1];
}

6.2二维差分

vector<vector<int>> s(n + 10, vector<int> (m + 10));
//...
//初始化完成后
function<void(int x1, int y1, int x2, int y2, int r)> insert = [&](int x1, int y1, int x2, int y2, int r) -> void {
    s[x1][y1] += r;
    s[x2 + 1][y1] -= r;
    s[x1][y2 + 1] -= r;
    s[x2 + 1][y2 + 1] += r;
}
for (int i = 1; i <= n; i ++) {
    for (int j = 1; j <= n; j ++) {
    	insert(i, j, i, j, s[i][j]);
    }
}

while (q --) {
    int x1, x2, y1, y2, r;
    cin >> x1 >> y1 >> x2 >> y2, r;
    insert(x1, y1, x2, y2, r);
}
//来个前缀和复原
for (int i = 1; i <= n; i ++) {
    for (int j = 1; j <= m; j ++) {
        s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    }
}

7.双指针

​ 常用做法

​ 1.两个指针从头一起走

​ 2.一个指针从头开始,一个指针从尾开始

8.位运算

8.1左移<<

在二进制表示下把数字同时向左移动,高位越界舍弃,低位补零。

8.2 右移>>

在二进制补码表示下把数字同时向右移动,高位以0补充,低位越界舍弃

8.3 与&

二进制表示下两位同时为1则为1,否则为0

8.4 或|

二进制表示下只要有一位为1则为1,否则为0

8.5 异或^

二进制表示下两个不同就为1,相同就为0

9. 离散化

这种题就是一些数字分布在一个很大的区间里,但是数字的总个数很少,开不了这么大的数组,怎么办呢,我们可以利用set和map来离散化,用set来把所有要用到的下标记录下来,然后用map把这些下标映射成1,2,3,4,5...n,然后把值加上去,这就是离散化。

这种感觉得多做题才会有感觉

10.区间合并

这个其实就相当于是一个操作了,能记住就行,记不住也能现推

先把所有的区间都存到一个vector里,然后,传入其中,里面的ed和seg.first的判断会因题做点小改变。

st和ed的初始值记住是极小值就行,因题有可能也会变

void merge(vector<pii> &segs) {
    vector<pii> res;
    int st = -2e9, ed = -2e9;
    sort(segs.begin(), segs.end());
    for (auto seg : segs) {
        if (ed < seg.first) {
            if (st != -2e9) {
                res.emplace_back(st, ed);
            }
            st = seg.first, ed = seg.second;
        } else {
            ed = max(ed, seg.second);
        }
    }
    if (st != -2e9) {
        res.emplace_back(st, ed);
    }
 	segs = res;
}
相关推荐
懒惰才能让科技进步27 分钟前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara41 分钟前
函数对象笔记
c++·算法
泉崎1 小时前
11.7比赛总结
数据结构·算法
你好helloworld1 小时前
滑动窗口最大值
数据结构·算法·leetcode
AI街潜水的八角2 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple2 小时前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少2 小时前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋3 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
咕咕吖4 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展
九圣残炎4 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode