算法分析与设计
算法概述
算法复杂度
五个渐近分析记号
注:以下的"情况"是指输入 \(n\) 具有的某种特性,比如下面例子里举的输入 \(n\) 为偶数的情况即是最坏情况,输入 \(n\) 为非2质数的情况即为最坏情况
1.1 渐近上界记号 \(\Omicron\)
即该算法在最坏情况下的时间复杂度
$\exists c, n_0, \forall n \geq n_0, 0 \leq f(n) \leq cg(n) $
\(f(n) = \Omicron(g(n))\)
\(\Leftrightarrow\)
\(\exists c > 0, \lim\limits_{n \rightarrow \infty} \frac{f(n)}{cg(n)} \leq 1^{-}\)
1.2 渐近下界记号 \(\Omega\)
即该算法在最好情况下的时间复杂度
$\exists c, n_0, \forall n \geq n_0, 0 \leq cg(n) \leq f(n) $
\(f(n) = \Omega(g(n))\)
\(\Leftrightarrow\)
\(\exists c > 0, \lim\limits_{n \rightarrow \infty} \frac{f(n)}{cg(n)} \geq 1^{+}\)
1.3 非紧上界记号 \(\omicron\)
即该算法在最坏情况下的时间复杂度小于该时间复杂度
$\forall \epsilon > 0, \exists n_0, \forall n \geq n_0, f(n) / g(n) < \epsilon $
\(f(n) = \omicron (g(n))\)
\(\Leftrightarrow\)
\(\lim\limits_{n \rightarrow \infty} \frac{f(n)}{g(n)} = 0\)
1.4 非紧下界记号 \(\omega\)
即该算法在最好情况下的时间复杂度大于该时间复杂度
$\forall \epsilon > 0, \exists n_0, \forall n \geq n_0, g(n) / f(n) < \epsilon $
\(f(n) = \omega (g(n))\)
\(\Leftrightarrow\)
\(\lim\limits_{n \rightarrow \infty} \frac{f(n)}{g(n)} = \infty\)
1.5 紧渐近界记号 \(\theta\)
无论什么情况,时间复杂度都相同
\(f(n) = \Omicron(g(n)) , f(n) = \Omega(g(n))\)
\(f(n) = \theta (g(n))\)
发现当 \(f(n)\) 为一确定的表达式时,\(\Omicron(f(n)) = \Omega(f(n))\)
那么什么情况下 \(\Omicron\) 和 \(\Omega\) 才不同呢?
关注下面素性检验(判断是否为质数)算法
bool isPrime(ll n) {
for (ll i = 2; i * i <= n; ++ i) {
if (n % i == 0) return 0;
}
return n > 1;
}
显然是一个根号算法 即\(\Omicron(f(n)) = O(n^{\frac{1}{2}})\)
但是当 n 是 偶数时,代码只有一次基本运算
即 \(\Omega(f(n)) = \Omega(1)\)
综上:大 \(\Omicron\) 关注“最多需要多少时间”,大 \(\Omega\) 关注“至少需要多少时间”
NP 完备性理论
判定问题:仅需回答YES或NO的问题
P 问题:所有可在多项式时间内求解的判定问题
NP 问题:能在多项式时间内验证解的判定问题
根据定义,显然有: \(P \subseteq NP\)
NPC问题:
递归和分治策略
递归
递归实现全排列
#include <bits/stdc++.h>
using namespace std;
void perm(vector<int> &a, int k, int n) {
if (k == n) {
for (int i = 0; i < n; ++ i) {
cout << a[i] << " \n"[i == n - 1];
}
} else {
for (int i = k; i < n; ++ i) {
swap(a[k], a[i]);
perm(a, k + 1, n);
swap(a[k], a[i]);
}
}
}
int main() {
int n;
cin >> n;
vector<int> a(n);
iota(a.begin(), a.end(), 1);
perm(a, 0, n);
}
整数划分
以下为动态规划实现
int DP(int n) { // O(n^2)
vector<vector<ll>> dp(n + 1, vector<ll>(n + 1));
dp[1][1] = 1;
for (int i = 2; i <= n; ++ i) {
dp[i][1] = 1;
for (int j = 2; j <= i - 1; ++ j) {
int x = i - j;
int y = min(j, i - j);
dp[i][j] += dp[x][y];
dp[i][j] += dp[i][j - 1];
}
dp[i][i] += dp[i][i - 1] + 1;
}
return dp[n][n];
}
以下为递归实现
int dfs0(int n, int m) {
if (n == 0 || m == 0) {
return 0;
} else if (n == 1 || m == 1) {
return 1;
} else if (n == m) {
return dfs0(n, m - 1) + 1;
} else {
int x = n - m;
int y = min(m, n - m);
return dfs0(x, y) + dfs0(n, m - 1);
}
}
int dfs(int n) { // O
return dfs0(n, n);
}
分治
二分搜索
int binsearch(vector<int> &a, int x) {
int l = 1, r = (int)a.size() - 1;
while (l <= r) {
int m = (l + r) >> 1;
if (x == a[m]) return m;
if (x < a[m]) {
r = m - 1;
} else {
l = m + 1;
}
}
return -1;
}
大整数乘法
这里直接封装了一个多项式类,其中base是进制,即该算法算法可以用于任意进制的大整数
下面讲解一下大整数乘法
\(x = a*10^{n / 2} + b\)
\(y = c*10^{n / 2} + d\)
\(x * y = ac*10^{n / 2 * 2} + ((a+b)(c+d) - ac - bd) * 10^{n / 2} + bd\)
(注意有的会写成 \(x * y = ac*10^{n} + ((a+b)(c+d) - ac - bd) * 10^{n / 2} + bd\) ,这是不严谨的)
然后递归进行乘法即可
注意当当前递归到的x, y的位数都为1时,需要直接计算出值作为递归的边界
/**
* title: bigmul.cpp
* Author: Proaes Meluam
* Date: 2025-06-03 21:50:56
* Describe:
* Link:
**/
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i32 = int;
using i64 = long long;
using i128 = __int128;
const double pi = acos(-1);
const double e = exp(1);
constexpr ll mod = 1e9 + 7;
// constexpr int inf = 0x3f3f3f3f;
constexpr ll inf = 0x3f3f3f3f3f3f3f3f;
class polynomial {
public:
ll n = 1, base = 10;
vector<ll> a;
polynomial() {
this-> n = 1;
a.resize(n);
}
polynomial(int n) {
this-> n = n;
a.resize(n);
}
void init(string s) {
for (int i = 0; i < n; ++ i) {
a[i] = s[n - i - 1] - '0';
}
}
void pad(int len) {
int cn = 1;
while (cn < len) cn <<= 1;
for (int i = n; i < cn; ++ i) a.emplace_back(0);
n = cn;
}
void clear0() {
while (n > 1 && a[n - 1] == 0) {
a.pop_back();
n--;
}
}
polynomial shift(int k) {
if (n + k < 0) return polynomial();
polynomial res(n + k);
for (int i = n - 1; i >= 0; -- i) {
res.a[i + k] = a[i];
}
res.clear0();
return res;
}
polynomial get(int l, int r) {
int d = r - l;
polynomial res(d + 1);
for (int i = 0; i + l <= r; ++ i) {
res.a[i] = a[l + i];
}
res.clear0();
return res;
}
static polynomial add(polynomial a, polynomial b) {
if (b.n > a.n) swap(a, b);
int cn = a.n;
polynomial res(cn);
ll carry = 0;
for (int i = 0; i < cn; ++ i) {
ll x = a.a[i], y = 0;
if (b.n > i) y = b.a[i];
res.a[i] += (x + y + carry) % res.base;
carry = (x + y + carry) / res.base;
}
if (carry) {
res.n ++;
res.a.emplace_back(carry);
}
res.clear0();
return res;
}
static polynomial sub(polynomial a, polynomial b) {
int cn = a.n;
polynomial res(cn);
ll carry = 0;
for (int i = 0; i < cn; ++ i) {
int cur = a.a[i];
if (b.n > i) cur -= b.a[i];
res.a[i] += cur;
if (res.a[i] < 0) {
res.a[i] += res.base;
res.a[i + 1] --;
}
}
res.clear0();
return res;
}
static polynomial mul3(polynomial x, polynomial y) {
if (x.n == 1 && y.n == 1) {
int num = x.a[0] * y.a[0];
int cn = (num >= 10 ? 2 : 1);
polynomial res(cn);
res.init(to_string(num));
return res;
}
int cn = max(x.n, y.n);
polynomial xp = x, yp = y;
xp.pad(cn); yp.pad(cn);
cn = max(xp.n, yp.n);
int m = cn >> 1;
polynomial x_low = xp.get(0, m - 1);
polynomial y_low = yp.get(0, m - 1);
polynomial x_high = xp.get(m, cn - 1);
polynomial y_high = yp.get(m, cn - 1);
polynomial z0 = mul3(x_low, y_low);
polynomial z2 = mul3(x_high, y_high);
polynomial x_sum = add(x_low, x_high);
polynomial y_sum = add(y_low, y_high);
polynomial z1 = mul3(x_sum, y_sum);
z1 = sub(z1, z0);
z1 = sub(z1, z2);
polynomial res;
res = add(res, z2.shift(2*m));
res = add(res, z1.shift(m));
res = add(res, z0);
res.clear0();
return res;
}
void show() {
for (int i = n - 1; i >= 0; -- i) {
cout << a[i];
}
cout << "\n";
}
};
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cout.tie(nullptr)->sync_with_stdio(false);
int n;
cin >> n;
polynomial x(n);
cin >> n;
polynomial y(n);
string s1, s2; cin >> s1 >> s2;
x.init(s1);
y.init(s2);
x.show();
y.show();
polynomial a = polynomial::add(x, y);
a.show();
polynomial b = polynomial::sub(x, y);
b.show();
polynomial c = polynomial::mul3(x, y);
c.show();
polynomial d;
d.show();
}
Strassen 矩阵乘法
若矩阵的维度不是2的幂次,那么先将矩阵维度改成2的幂次再应用该算法,具体见书本