七、数据结构
1. 树状数组
只能解决值符合逆运算的数据区间
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
ll bt[N]={0};
int lowbit(int x)//位置计算
{
return x & -x;
}
void add(int p, int k)//单点添加
{
while (p <= n)
{
bt[p] += k;
p += lowbit(p);
}
}
ll askInterval(int p)//区间查询,查询1-p的区间和
{
ll res = 0;
while (p > 0)
{
res += bt[p];
p -= lowbit(p);
}
return res;
}
void build()
{
int n;cin>>n;
for(int i=1;i<=n;i++)
{
cin>>bt[i];
add(i,bt[i]);
}
}
//差分优化
ll a[N]={0};//存原数据,树状数组存变化值
int lowbit(int x)
{
return x & -x;
}
void add(int p, int k)// 单点加
{
while (p <= n)
{
bt[p] += k;
p += lowbit(p);
}
}
ll askInerval(int p)//区间查,查询1-p的区间和
{
ll res = a[p];
while (p > 0)
{
res += bt[p];
p -= lowbit(p);
}
return res;
}
void addInterval(int x, int y, int k)// 区间加,使用差分进行区间加
{
add(x, k);
add(y + 1, -k);
}
2. 线段树
#include <iostream>
#include <queue>
#define lt p*2
#define rt p*2+1
#define mid (l+r)>>1
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int ls = 0, rs = 1;
struct Node
{
ll v;
ll add;
ll mul;
}st[4 * N];
ll nums[N]{ 0 };
int n, q, mod;
void pushup(int p)//向上更新线段树
{
st[p].v = (st[lt].v + st[rt].v) % mod;
}
void build(int l, int r, int p)//建树
{
st[p].add = 0;
st[p].mul = 1;
if (l == r)
{
st[p].v = nums[l];
return;
}
int m = mid;
build(l, m, lt);
build(m + 1, r, rt);
pushup(p);
}
void pushdown(int l, int r, int p)//向下更新标记
{
int m = mid;
st[lt].v = (st[lt].v * st[p].mul + st[p].add * (m - l + 1)) % mod;
st[rt].v = (st[rt].v * st[p].mul + st[p].add * (r - m)) % mod;
st[lt].mul = (st[lt].mul * st[p].mul) % mod;
st[rt].mul = (st[rt].mul * st[p].mul) % mod;
st[lt].add = (st[lt].add * st[p].mul + st[p].add) % mod;
st[rt].add = (st[rt].add * st[p].mul + st[p].add) % mod;
st[p].mul = 1;
st[p].add = 0;
}
ll sumInterval(int x, int y, int l, int r, int p)//区间和
{
if (r < x || y < l)
return 0;
if (x <= l && r <= y)
return st[p].v;
pushdown(l, r, p);
int m = mid;
ll sum = 0;
if (x <= m)
sum = (sum + sumInterval(x, y, l, m, p * 2)) % mod;
if (m < y)
sum += (sumInterval(x, y, m + 1, r, p * 2 + 1)) % mod;
return sum % mod;
}
void addInterval(int x, int y, int k, int l, int r, int p)//区间加
{
if (r < x || y < l)
return;
if (x <= l && r <= y)
{
st[p].add = (st[p].add + k) % mod;
st[p].v = (st[p].v + k * (r - l + 1)) % mod;
return;
}
pushdown(l, r, p);
int m = mid;
if (x <= m)
addInterval(x, y, k, l, m, lt);
if (m < y)
addInterval(x, y, k, m + 1, r, rt);
pushup(p);
}
void mulInterval(int x, int y, int k, int l, int r, int p)//区间乘
{
if (r < x || y < l)
return;
if (x <= l && r <= y)
{
st[p].mul = (st[p].mul * k) % mod;
st[p].add = (st[p].add * k) % mod;
st[p].v = (st[p].v * k) % mod;
return;
}
pushdown(l, r, p);
int m = mid;
if (x <= m)
mulInterval(x, y, k, l, m, lt);
if (mid < y)
mulInterval(x, y, k, m + 1, r, rt);
pushup(p);
}
void solve()
{
cin >> n >> q >> mod;
for (int i = 1; i <= n; i++)
cin >> nums[i];
build(1, n, 1);
int opt, x, y;
ll k;
while (q--)
{
cin >> opt >> x >> y;
switch (opt)
{
case 1:
cin >> k;
mulInterval(x, y, k, 1, n, 1);
break;
case 2:
cin >> k;
addInterval(x, y, k, 1, n, 1);
break;
case 3:
cout << sumInterval(x, y, 1, n, 1) << '\n';
break;
default:
break;
}
}
}
3. 珂朵莉树
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
typedef long long ll;
const int N = 5e5 + 5;
const int mod = 1e9 + 7;
int n, q, opt, l1, r1, l2, r2, l, r;
ll val, temp;
struct node
{
int _left, _right;
mutable ll _val;
node(int L = 0, int R = -1, long long V = 0) :_left(L), _right(R), _val(V) {}
bool operator < (const node& x) const
{
return _left < x._left;
}
}a[N], b[N];
set<node> s;
auto split(int p)//分割
{
auto iter = s.lower_bound(node(p));
if (iter != s.end() && iter->_left == p)
return iter;
iter--;
int l = iter->_left;
int r = iter->_right;
ll v = iter->_val;
s.erase(iter);
s.insert(node(l, p - 1, v));
return s.insert(node(p, r, v)).first;
}
ll askInterval(int l, int r)//区间和查询
{
ll ans = 0;
auto itR = split(r + 1);
auto itL = split(l);
for (auto iter = itL; iter != itR; ++iter)
{
ans += (ll)(iter->_right - iter->_left + 1) * iter->_val;
ans %= mod;
}
return ans;
}
void intervalAdd(int l, int r, ll v)//区间添加
{
auto itR = split(r + 1);
auto itL = split(l);
for (auto iter = itL; iter != itR; iter++)
{
iter->_val += v;
iter->_val %= mod;
}
}
void tpInterval(int l, int r, ll v)//推平
{
auto itR = split(r + 1);
auto itL = split(l);
s.erase(itL, itR);
s.insert(node(l, r, v));
}
void copyInterval(int l1, int r1, int l2, int r2)//复制
{
auto itR1 = split(r1 + 1);//先切右再切左
auto itL1 = split(l1);
int len = 0;
for (auto iter = itL1; iter != itR1; ++iter)
{
a[++len]._left = iter->_left;
a[len]._right = iter->_right;
a[len]._val = iter->_val;
}
auto itR2 = split(r2 + 1);
auto itL2 = split(l2);
s.erase(itL2, itR2);
for (int i = 1; i <= len; ++i)
{
s.insert(node(a[i]._left - l1 + l2, a[i]._right - l1 + l2, a[i]._val));
}
}
void printKodoriTree()//打印
{
for (auto it = s.begin(); it != s.end() && it->_right <= n; ++it)
{
for (int i = it->_left; i <= it->_right; ++i)
{
printf("%lld ", it->_val);
}
}
}
void swapInterval(int l1, int r1, int l2, int r2)//区间交换
{
if (l1 > l2)
{
swap(l1, l2);
swap(r1, r2);
}
int len1 = 0, len2 = 0;
auto itR1 = split(r1 + 1);
auto itL1 = split(l1);
for (auto iter = itL1; iter != itR1; ++iter)
{
a[++len1]._left = iter->_left;
a[len1]._right = iter->_right;
a[len1]._val = iter->_val;
}
auto itR2 = split(r2 + 1);
auto itL2 = split(l2);
for (auto iter = itL2; iter != itR2; ++iter)
{
b[++len2]._left = iter->_left;
b[len2]._right = iter->_right;
b[len2]._val = iter->_val;
}
itR1 = split(r1 + 1);
itL1 = split(l1);
s.erase(itL1, itR1);
itR2 = split(r2 + 1);
itL2 = split(l2);
s.erase(itL2, itR2);
for (int i = 1; i <= len2; ++i)
{
s.insert(node(b[i]._left - l2 + l1, b[i]._right - l2 + l1, b[i]._val));
}
for (int i = 1; i <= len1; ++i)
{
s.insert(node(a[i]._left - l1 + l2, a[i]._right - l1 + l2, a[i]._val));
}
}
void reverseInterval(int l, int r)//区间逆置
{
if (l > r)
swap(l, r);
auto itR = split(r + 1);
auto itL = split(l);
int len = 0;
for (auto iter = itL; iter != itR; ++iter)
{
a[++len]._left = iter->_left;
a[len]._right = iter->_right;
a[len]._val = iter->_val;
}
s.erase(itL, itR);
for (int i = 1; i <= len; ++i)
{
s.insert(node(r - a[i]._right + l, r - a[i]._left + l, a[i]._val));
}
}
void solve()
{
cin >> n >> q;
for (int i = 1; i <= n; ++i)//构建珂朵莉树
{
cin >> temp;
s.insert(node(i, i, temp));
}
while (q--)
{
cin >> opt;
if (opt == 1)
{
cin >> l >> r;
printf("%lld\n", askInterval(l, r));
}
else if (opt == 2)
{
cin >> l >> r >> val;
tpInterval(l, r, val);
}
else if (opt == 3)
{
cin >> l >> r >> val;
intervalAdd(l, r, val);
}
else if (opt == 4)
{
cin >> l1 >> r1 >> l2 >> r2;
copyInterval(l1, r1, l2, r2);
}
else if (opt == 5)
{
cin >> l1 >> r1 >> l2 >> r2;
swapInterval(l1, r1, l2, r2);
}
else if (opt == 6)
{
cin >> l >> r;
reverseInterval(l, r);
}
}
printKodoriTree();
}
4. 并查集
功能: 用于处理不相交集合的数据结构,支持合并两个集合和查找元素所属的集合。
#include <iostream>
#include <vector>
using namespace std;
class UnionFind {
public:
vector<int> parent, rank;
// 构造函数,初始化并查集
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
// 查找元素所属的集合
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并两个集合
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
};
// 示例用法
int main() {
int n = 5; // 假设有5个元素
UnionFind uf(n);
// 在实际问题中,根据需要执行一系列的 find 和 union 操作
return 0;
}
5.单调栈
功能: 用于在一个序列中找到每个元素的下一个更大元素。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
/**
* 单调栈
* @param nums 输入序列
* @return 每个元素的下一个更大元素
*/
vector<int> nextGreaterElement(const vector<int>& nums) {
int n = nums.size();
vector<int> result(n, -1);
stack<int> s;
for (int i = 0; i < n; ++i) {
while (!s.empty() && nums[i] > nums[s.top()]) {
result[s.top()] = nums[i];
s.pop();
}
s.push(i);
}
return result;
}
// 示例用法
int main() {
vector<int> nums = {2, 1, 2, 4, 3};
vector<int> result = nextGreaterElement(nums);
// 在实际问题中,根据需要使用得到的结果
return 0;
}
6. 单调队列
功能: 用于在一个序列中找到每个窗口的最大值。
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
/**
* 单调队列
* @param nums 输入序列
* @param k 窗口大小
* @return 每个窗口的最大值
*/
vector<int> maxSlidingWindow(const vector<int>& nums, int k) {
int n = nums.size();
vector<int> result;
deque<int> dq;
for (int i = 0; i < n; ++i) {
// 移除超出窗口范围的元素
while (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
// 维护单调递减队列
while (!dq.empty() && nums[i] > nums[dq.back()]) {
dq.pop_back();
}
dq.push_back(i);
// 将当前窗口最大值加入结果
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}
// 示例用法
int main() {
vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
vector<int> result = maxSlidingWindow(nums, k);
// 在实际问题中,根据需要使用得到的结果
return 0;
}
7. ST 表
功能: 用于在数组中进行区间查询,支持快速查询区间最值。
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
class SegmentTree {
public:
vector<int> tree;
int n;
// 构造函数,初始化线段树
SegmentTree(const vector<int>& nums) {
n = nums.size();
int height = ceil(log2(n));
int treeSize = 2 * pow(2, height) - 1;
tree.resize(treeSize, 0);
buildTree(nums, 0, 0, n - 1);
}
// 构建线段树
void buildTree(const vector<int>& nums, int index, int start, int end) {
if (start == end) {
tree[index] = nums[start];
return;
}
int mid = start + (end - start) / 2;
buildTree(nums, 2 * index + 1, start, mid);
buildTree(nums, 2 * index + 2, mid + 1, end);
tree[index] = min(tree[2 * index + 1], tree[2 * index + 2]); // 修改这里以适应其他查询需求
}
// 区间查询
int query(int index, int start, int end, int qStart, int qEnd) {
if (qStart > end || qEnd < start) {
// 当前区间与查询区间无交集
return INT_MAX; // 修改这里以适应其他查询需求
}
if (qStart <= start && qEnd >= end) {
// 当前区间完全包含在查询区间中
return tree[index];
}
// 查询左右子树
int mid = start + (end - start) / 2;
int leftMin = query(2 * index + 1, start, mid, qStart, qEnd);
int rightMin = query(2 * index + 2, mid + 1, end, qStart, qEnd);
return min(leftMin, rightMin); // 修改这里以适应其他查询需求
}
};
// 示例用法
int main() {
vector<int> nums = {1, 3, 2, 7, 9, 11};
SegmentTree st(nums);
// 在实际问题中,根据需要执行一系列的查询操作
return 0;
}
8. 树状数组
功能: 用于高效实现前缀和的数据结构。
#include <iostream>
#include <vector>
using namespace std;
class FenwickTree {
public:
vector<int> BIT;
// 构造函数,初始化树状数组
FenwickTree(int n) {
BIT.resize(n + 1, 0);
}
// 更新指定位置的值
void update(int index, int delta) {
index++;
while (index < BIT.size()) {
BIT[index] += delta;
index += index & -index;
}
}
// 计算前缀和
int query(int index) {
index++;
int sum = 0;
while (index > 0) {
sum += BIT[index];
index -= index & -index;
}
return sum;
}
};
// 示例用法
int main() {
int n = 6;
FenwickTree fenwickTree(n);
// 在实际问题中,根据需要执行一系列的更新和查询操作
return 0;
}
9. 线段树
功能: 用于在数组中进行区间查询和更新,支持快速查询区间信息。
#include <iostream>
#include <vector>
using namespace std;
class SegmentTree {
public:
vector<int> tree;
int n;
// 构造函数,初始化线段树
SegmentTree(const vector<int>& nums) {
n = nums.size();
tree.resize(4 * n, 0);
buildTree(nums, 0, 0, n - 1);
}
// 构建线段树
void buildTree(const vector<int>& nums, int index, int start, int end) {
if (start == end) {
tree[index] = nums[start];
return;
}
int mid = start + (end - start) / 2;
buildTree(nums, 2 * index + 1, start, mid);
buildTree(nums, 2 * index + 2, mid + 1, end);
tree[index] = tree[2 * index + 1] + tree[2 * index + 2]; // 修改这里以适应其他查询需求
}
// 区间查询
int query(int index, int start, int end, int qStart, int qEnd) {
if (qStart > end || qEnd < start) {
// 当前区间与查询区间无交集
return 0; // 修改这里以适应其他查询需求
}
if (qStart <= start && qEnd >= end) {
// 当前区间完全包含在查询区间中
return tree[index];
}
// 查询左右子树
int mid = start + (end - start) / 2;
int leftSum = query(2 * index + 1, start, mid, qStart, qEnd);
int rightSum = query(2 * index + 2, mid + 1, end, qStart, qEnd);
return leftSum + rightSum; // 修改这里以适应其他查询需求
}
// 区间更新
void update(int index, int start, int end, int updateIndex, int val) {
if (start == end) {
// 更新叶节点
tree[index] = val;
return;
}
int mid = start + (end - start) / 2;
if (updateIndex <= mid) {
// 在左子树中更新
update(2 * index + 1, start, mid, updateIndex, val);
} else {
// 在右子树中更新
update(2 * index + 2, mid + 1, end, updateIndex, val);
}
// 更新父节点
tree[index] = tree[2 * index + 1] + tree[2 * index + 2]; // 修改这里以适应其他查询需求
}
};
// 示例用法
int main() {
vector<int> nums = {1, 3, 5, 7, 9, 11};
SegmentTree st(nums);
// 在实际问题中,根据需要执行一系列的查询和更新操作
return 0;
}
10. 李超线段树
功能: 用于维护动态区间的斜率,支持查询给定 x 处最大/最小值。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Line {
long long slope, intercept;
long long eval(long long x) const {
return slope * x + intercept;
}
};
class LiChaoTree {
public:
vector<Line> tree;
int n;
// 构造函数,初始化李超线段树
LiChaoTree(int size) {
n = size;
tree.resize(4 * n);
}
// 插入线段
void insert(int index, int start, int end, Line& line) {
int mid = start + (end - start) / 2;
bool left = line.eval(start) < tree[index].eval(start);
bool midPoint = line.eval(mid) < tree[index].eval(mid);
if (midPoint) {
swap(tree[index], line);
}
if (end - start == 0) {
return;
} else if (left != midPoint) {
insert(2 * index + 1, start, mid, line);
} else {
insert(2 * index + 2, mid + 1, end, line);
}
}
// 查询给定 x 处的最大/最小值
long long query(int index, int start, int end, int x) const {
int mid = start + (end - start) / 2;
long long result = tree[index].eval(x);
if (end - start == 0) {
return result;
} else if (x <= mid) {
return max(result, query(2 * index + 1, start, mid, x));
} else {
return max(result, query(2 * index + 2, mid + 1, end, x));
}
}
};
// 示例用法
int main() {
int n = 5; // 假设有5个线段
LiChaoTree liChaoTree(n);
// 在实际问题中,根据需要插入线段并进行查询
return 0;
}
11. 划分树
功能: 用于在数组中进行区间查询,支持查询第 K 小(大)的元素。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class DivisionTree {
public:
vector<vector<int>> tree;
// 构造函数,初始化划分树
DivisionTree(const vector<int>& nums) {
int n = nums.size();
tree.resize(4 * n);
buildTree(nums, 0, 0, n - 1);
}
// 构建划分树
void buildTree(const vector<int>& nums, int index, int start, int end) {
if (start == end) {
tree[index].push_back(nums[start]);
return;
}
int mid = start + (end - start) / 2;
buildTree(nums, 2 * index + 1, start, mid);
buildTree(nums, 2 * index + 2, mid + 1, end);
merge(tree[2 * index + 1].begin(), tree[2 * index + 1].end(),
tree[2 * index + 2].begin(), tree[2 * index + 2].end(),
back_inserter(tree[index]));
}
// 查询第 K 小的元素
int query(int index, int start, int end, int qStart, int qEnd, int k) {
if (start == end) {
return tree[index][0];
}
int mid = start + (end - start) / 2;
int leftCount = lower_bound(tree[2 * index + 1].begin(), tree[2 * index + 1].end(), qStart) - tree[2 * index + 1].begin();
int rightCount = lower_bound(tree[2 * index + 2].begin(), tree[2 * index + 2].end(), qStart) - tree[2 * index + 2].begin();
if (rightCount - leftCount >= k) {
return query(2 * index + 1, start, mid, qStart, qEnd, k);
} else {
return query(2 * index + 2, mid + 1, end, qStart, qEnd, k - (rightCount - leftCount));
}
}
};
// 示例用法
int main() {
vector<int> nums = {4, 2, 7, 3, 1, 6, 5};
DivisionTree divisionTree(nums);
// 在实际问题中,根据需要执行一系列的查询操作
return 0;
}
12. 二叉搜索树 & 平衡树
功能: 用于在动态数据集合上进行插入、删除和查询等操作,保持有序性。
#include <iostream>
#include <set>
using namespace std;
// 示例用法
int main() {
// 使用 C++ STL 中的 set 实现二叉搜索树
set<int> bst;
// 插入元素
bst.insert(5);
bst.insert(3);
bst.insert(7);
// 删除元素
bst.erase(3);
// 查询元素
auto it = bst.find(5);
if (it != bst.end()) {
cout << "Element found: " << *it << endl;
}
// 在实际问题中,根据需要执行一系列的插入、删除和查询操作
return 0;
}
13. 跳表
功能: 用于在有序链表中进行快速查询,支持插入和删除操作。
#include <iostream>
#include <vector>
using namespace std;
class Node {
public:
int value;
vector<Node*> next;
Node(int val, int level) : value(val), next(level, nullptr) {}
};
class SkipList {
public:
int maxLevel;
Node* head;
SkipList() {
maxLevel = 16; // 可调整最大层数
head = new Node(INT_MIN, maxLevel);
}
// 随机生成层数
int randomLevel() const {
int level = 1;
while ((rand() % 2 == 0) && (level < maxLevel)) {
level++;
}
return level;
}
// 插入元素
void insert(int value) {
int level = randomLevel();
Node* newNode = new Node(value, level);
Node* current = head;
for (int i = maxLevel - 1; i >= 0; i--) {
while (current->next[i] != nullptr && current->next[i]->value < value) {
current = current->next[i];
}
if (i < level) {
newNode->next[i] = current->next[i];
current->next[i] = newNode;
}
}
}
// 删除元素
void erase(int value) {
Node* current = head;
vector<Node*> update(maxLevel, nullptr);
for (int i = maxLevel - 1; i >= 0; i--) {
while (current->next[i] != nullptr && current->next[i]->value < value) {
current = current->next[i];
}
update[i] = current;
}
if (current->next[0] != nullptr && current->next[0]->value == value) {
Node* toDelete = current->next[0];
for (int i = 0; i < toDelete->next.size(); i++) {
update[i]->next[i] = toDelete->next[i];
}
delete toDelete;
}
}
// 查询元素
bool search(int value) const {
Node* current = head;
for (int i = maxLevel - 1; i >= 0; i--) {
while (current->next[i] != nullptr && current->next[i]->value < value) {
current = current->next[i];
}
}
return (current->next[0] != nullptr && current->next[0]->value == value);
}
};
// 示例用法
int main() {
SkipList skipList;
// 在实际问题中,根据需要执行一系列的插入、删除和查询操作
return 0;
}
14. 树套树
功能: 用于在树状结构上进行区间查询和更新。
#include <iostream>
#include <vector>
using namespace std;
class TreeArray {
public:
vector<int> tree;
// 构造函数,初始化树状数组
TreeArray(int n) {
tree.resize(n + 1, 0);
}
// 单点更新
void update(int index, int delta) {
index++;
while (index < tree.size()) {
tree[index] += delta;
index += index & -index;
}
}
// 区间查询
int query(int index) {
index++;
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= index & -index;
}
return sum;
}
};
class TreeArray2D {
public:
vector<TreeArray> treeArray;
// 构造函数,初始化二维树状数组
TreeArray2D(int n, int m) {
treeArray.resize(n + 1, TreeArray(m));
}
// 单点更新
void update(int x, int y, int delta) {
x++;
while (x < treeArray.size()) {
treeArray[x].update(y, delta);
x += x & -x;
}
}
// 区间查询
int query(int x, int y) {
x++;
int sum = 0;
while (x > 0) {
sum += treeArray[x].query(y);
x -= x & -x;
}
return sum;
}
};
// 示例用法
int main() {
int n = 5, m = 5; // 假设有一个5x5的矩阵
TreeArray2D treeArray2D(n, m);
// 在实际问题中,根据需要执行一系列的更新和查询操作
return 0;
}
15. K-D Tree
功能: 用于在 k 维空间中存储和搜索数据点。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Point {
public:
vector<int> coordinates;
Point(const vector<int>& coords) : coordinates(coords) {}
};
class KDNode {
public:
Point point;
KDNode* left;
KDNode* right;
KDNode(const Point& p) : point(p), left(nullptr), right(nullptr) {}
};
class KDTree {
public:
int k; // 维度
KDNode* root;
// 构造函数,初始化 K-D 树
KDTree(int dimension) : k(dimension), root(nullptr) {}
// 构建 K-D 树
KDNode* buildTree(vector<Point>& points, int depth) {
if (points.empty()) {
return nullptr;
}
int axis = depth % k;
auto compare = [axis](const Point& a, const Point& b) {
return a.coordinates[axis] < b.coordinates[axis];
};
int medianIndex = points.size() / 2;
nth_element(points.begin(), points.begin() + medianIndex, points.end(), compare);
KDNode* node = new KDNode(points[medianIndex]);
node->left = buildTree(vector<Point>(points.begin(), points.begin() + medianIndex), depth + 1);
node->right = buildTree(vector<Point>(points.begin() + medianIndex + 1, points.end()), depth + 1);
return node;
}
// 公共接口,构建 K-D 树
void build(vector<Point>& points) {
root = buildTree(points, 0);
}
// 查询 K-D 树中距离最近的点
Point findNearestNeighbor(const Point& target) {
if (!root) {
cerr << "K-D tree is empty." << endl;
return Point(vector<int>());
}
Point best = root->point;
double bestDist = distance(target, best);
searchNearestNeighbor(root, target, 0, best, bestDist);
return best;
}
private:
// 计算两点之间的欧氏距离
double distance(const Point& a, const Point& b) {
double result = 0.0;
for (int i = 0; i < k; ++i) {
double diff = a.coordinates[i] - b.coordinates[i];
result += diff * diff;
}
return result;
}
// 递归搜索 K-D 树中距离最近的点
void searchNearestNeighbor(KDNode* node, const Point& target, int depth, Point& best, double& bestDist) {
if (!node) {
return;
}
int axis = depth % k;
KDNode* next = (target.coordinates[axis] < node->point.coordinates[axis]) ? node->left : node->right;
KDNode* other = (target.coordinates[axis] < node->point.coordinates[axis]) ? node->right : node->left;
searchNearestNeighbor(next, target, depth + 1, best, bestDist);
double nodeDist = distance(target, node->point);
if (nodeDist < bestDist) {
best = node->point;
bestDist = nodeDist;
}
if (fabs(target.coordinates[axis] - node->point.coordinates[axis]) < bestDist) {
searchNearestNeighbor(other, target, depth + 1, best, bestDist);
}
}
};
// 示例用法
int main() {
vector<Point> points = {Point({2, 3}), Point({5, 4}), Point({9, 6}), Point({4, 7}), Point({8, 1}), Point({7, 2})};
KDTree kdTree(2);
kdTree.build(points);
Point target({9, 2});
Point nearestNeighbor = kdTree.findNearestNeighbor(target);
// 在实际问题中,根据需要执行一系列的查询操作
return 0;
}
16. 动态树
功能: 用于维护一个动态的树结构,支持插入、删除、合并等操作。
#include <iostream>
#include <vector>
using namespace std;
class DynamicTreeNode {
public:
int key;
int priority;
int size;
DynamicTreeNode* left;
DynamicTreeNode* right;
DynamicTreeNode(int k) : key(k), priority(rand()), size(1), left(nullptr), right(nullptr) {}
};
class DynamicTree {
public:
DynamicTreeNode* root;
DynamicTree() : root(nullptr) {}
// 获取节点的大小
int getSize(DynamicTreeNode* node) {
return node ? node->size : 0;
}
// 更新节点的大小
void updateSize(DynamicTreeNode* node) {
if (node) {
node->size = getSize(node->left) + getSize(node->right) + 1;
}
}
// 左旋操作
DynamicTreeNode* leftRotate(DynamicTreeNode* node) {
DynamicTreeNode* newRoot = node->right;
node->right = newRoot->left;
newRoot->left = node;
updateSize(node);
updateSize(newRoot);
return newRoot;
}
// 右旋操作
DynamicTreeNode* rightRotate(DynamicTreeNode* node) {
DynamicTreeNode* newRoot = node->left;
node->left = newRoot->right;
newRoot->right = node;
updateSize(node);
updateSize(newRoot);
return newRoot;
}
// 插入节点
DynamicTreeNode* insert(DynamicTreeNode* node, int key) {
if (!node) {
return new DynamicTreeNode(key);
}
if (key < node->key) {
node->left = insert(node->left, key);
if (node->left->priority > node->priority) {
node = rightRotate(node);
}
} else {
node->right = insert(node->right, key);
if (node->right->priority > node->priority) {
node = leftRotate(node);
}
}
updateSize(node);
return node;
}
// 删除节点
DynamicTreeNode* erase(DynamicTreeNode* node, int key) {
if (!node) {
return nullptr;
}
if (key == node->key) {
DynamicTreeNode* temp = node;
node = merge(node->left, node->right);
delete temp;
} else if (key < node->key) {
node->left = erase(node->left, key);
} else {
node->right = erase(node->right, key);
}
updateSize(node);
return node;
}
// 合并两棵树
DynamicTreeNode* merge(DynamicTreeNode* left, DynamicTreeNode* right) {
if (!left || !right) {
return left ? left : right;
}
if (left->priority > right->priority) {
left->right = merge(left->right, right);
updateSize(left);
return left;
} else {
right->left = merge(left, right->left);
updateSize(right);
return right;
}
}
// 插入节点的公共接口
void insert(int key) {
root = insert(root, key);
}
// 删除节点的公共接口
void erase(int key) {
root = erase(root, key);
}
};
// 示例用法
int main() {
DynamicTree dynamicTree;
// 在实际问题中,根据需要执行一系列的插入、删除等操作
return 0;
}
17. 析合树
功能: 用于在线性序列上进行范围查询,支持插入和删除操作。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class DisjointSetUnion {
public:
vector<int> parent;
vector<int> size;
// 构造函数,初始化并查集
DisjointSetUnion(int n) {
parent.resize(n);
size.resize(n, 1);
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
// 查找根节点
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
// 合并两个集合
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (size[rootX] < size[rootY]) {
swap(rootX, rootY);
}
parent[rootY] = rootX;
size[rootX] += size[rootY];
}
}
};
class MergeTree {
public:
vector<DisjointSetUnion> forest;
// 构造函数,初始化析合树
MergeTree(int n) : forest(n, DisjointSetUnion(1)) {}
// 插入元素
void insert(int version, int x) {
forest[version].parent.push_back(x);
}
// 删除元素
void erase(int version, int x) {
forest[version].parent[x] = x;
}
// 查询元素所在的集合大小
int query(int version, int x) {
int root = forest[version].find(x);
return forest[version].size[root];
}
// 合并版本
void merge(int v1, int v2) {
forest[v1].parent.insert(forest[v1].parent.end(), forest[v2].parent.begin(), forest[v2].parent.end());
for (int i = 0; i < forest[v2].parent.size(); ++i) {
int root = forest[v2].find(i);
forest[v1].size[root] += forest[v2].size[i];
}
}
};
// 示例用法
int main() {
int n = 5; // 假设有5个元素
MergeTree mergeTree(n);
// 插入元素
mergeTree.insert(0, 1);
mergeTree.insert(0, 2);
// 删除元素
mergeTree.erase(0, 1);
// 查询元素所在的集合大小
int size = mergeTree.query(0, 2);
// 合并版本
mergeTree.merge(1, 0);
// 在实际问题中,根据需要执行一系列的操作
return 0;
}
18. PQ 树
功能: 用于在字符串上进行子串同构判定。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
class PQNode {
public:
PQNode* left;
PQNode* right;
int type; // 0: leaf, 1: P-node, 2: Q-node
char label; // 仅对叶节点有效
int id; // 节点标识
PQNode(int t, char l, int i) : type(t), label(l), id(i), left(nullptr), right(nullptr) {}
};
class PQTree {
public:
PQNode* root;
int idCounter;
// 构造函数,初始化 PQ 树
PQTree() : root(nullptr), idCounter(1) {}
// 构建 PQ 树
PQNode* buildPQTree(const string& s) {
if (s.empty()) {
return nullptr;
}
if (s.size() == 1) {
return new PQNode(0, s[0], idCounter++);
}
PQNode* node = new PQNode(1, 'P', idCounter++);
int count = 0;
int i = 0;
while (count != 0 || i == 0) {
if (s[i] == '0') {
count--;
} else {
count++;
}
i++;
}
node->left = buildPQTree(s.substr(1, i - 2));
node->right = buildPQTree(s.substr(i));
return node;
}
// 判断两个 PQ 树是否同构
bool isomorphic(PQNode* root1, PQNode* root2) {
if (!root1 && !root2) {
return true;
}
if (!root1 || !root2) {
return false;
}
if (root1->type == 0 && root2->type == 0) {
return true;
}
if (root1->type == 0 || root2->type == 0) {
return false;
}
if (root1->label != root2->label) {
return false;
}
return (isomorphic(root1->left, root2->left) && isomorphic(root1->right, root2->right)) ||
(isomorphic(root1->left, root2->right) && isomorphic(root1->right, root2->left));
}
// 公共接口,构建 PQ 树
void build(const string& s) {
root = buildPQTree(s);
}
// 公共接口,判断两个 PQ 树是否同构
bool isomorphic(PQTree& other) {
return isomorphic(root, other.root);
}
};
// 示例用法
int main() {
PQTree pqTree1, pqTree2;
// 构建 PQ 树
pqTree1.build("10");
pqTree2.build("10");
// 判断两个 PQ 树是否同构
bool isIsomorphic = pqTree1.isomorphic(pqTree2);
// 在实际问题中,根据需要执行一系列的操作
return 0;
}
19. 手指树
功能: 用于支持序列的快速查询和更新操作,结合了平衡树和树状数组的优点。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class FingerTree {
public:
vector<int> array;
vector<int> sortedArray;
vector<int> countArray;
// 构造函数,初始化手指树
FingerTree(const vector<int>& input) : array(input) {
sortedArray = input;
sort(sortedArray.begin(), sortedArray.end());
countArray.resize(sortedArray.size(), 0);
build(0, array.size() - 1, 0);
}
// 查询小于等于某个值的元素个数
int query(int x) {
auto it = upper_bound(sortedArray.begin(), sortedArray.end(), x);
int index = it - sortedArray.begin();
return query(0, array.size() - 1, 0, index);
}
// 区间更新
void update(int left, int right, int oldValue, int newValue) {
update(0, array.size() - 1, 0, left, right, oldValue, newValue);
}
private:
// 构建手指树
void build(int left, int right, int index) {
if (left == right) {
countArray[index] = (array[left] == sortedArray[index] ? 1 : 0);
return;
}
int mid = left + (right - left) / 2;
build(left, mid, 2 * index + 1);
build(mid + 1, right, 2 * index + 2);
countArray[index] = countArray[2 * index + 1] + countArray[2 * index + 2];
}
// 查询小于等于某个值的元素个数
int query(int left, int right, int index, int targetIndex) {
if (left == right) {
return countArray[index];
}
int mid = left + (right - left) / 2;
if (targetIndex <= mid) {
return query(left, mid, 2 * index + 1, targetIndex);
} else {
return countArray[2 * index + 1] + query(mid + 1, right, 2 * index + 2, targetIndex);
}
}
// 区间更新
void update(int left, int right, int index, int queryLeft, int queryRight, int oldValue, int newValue) {
if (right < queryLeft || left > queryRight) {
return;
}
if (left == right) {
array[left] = newValue;
countArray[index] = (newValue == sortedArray[index] ? 1 : 0);
return;
}
int mid = left + (right - left) / 2;
update(left, mid, 2 * index + 1, queryLeft, queryRight, oldValue, newValue);
update(mid + 1, right, 2 * index + 2, queryLeft, queryRight, oldValue, newValue);
countArray[index] = countArray[2 * index + 1] + countArray[2 * index + 2];
}
};
// 示例用法
int main() {
vector<int> input = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
FingerTree fingerTree(input);
// 查询小于等于某个值的元素个数
int count = fingerTree.query(4);
// 区间更新
fingerTree.update(2, 8, 4, 7);
// 在实际问题中,根据需要执行一系列的查询和更新操作
return 0;
}
20. 霍夫曼树
功能: 用于构建霍夫曼编码树,实现数据的压缩。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class HuffmanNode {
public:
char data; // 数据,对于内部节点可为空字符
int frequency; // 频率
HuffmanNode* left;
HuffmanNode* right;
HuffmanNode(char d, int f) : data(d), frequency(f), left(nullptr), right(nullptr) {}
};
struct CompareNodes {
bool operator()(HuffmanNode* a, HuffmanNode* b) {
return a->frequency > b->frequency;
}
};
class HuffmanTree {
public:
HuffmanNode* root;
// 构造函数,根据给定频率构建霍夫曼树
HuffmanTree(const vector<int>& frequencies, const vector<char>& data) {
priority_queue<HuffmanNode*, vector<HuffmanNode*>, CompareNodes> pq;
for (int i = 0; i < frequencies.size(); ++i) {
pq.push(new HuffmanNode(data[i], frequencies[i]));
}
while (pq.size() > 1) {
HuffmanNode* left = pq.top();
pq.pop();
HuffmanNode* right = pq.top();
pq.pop();
HuffmanNode* internalNode = new HuffmanNode('\0', left->frequency + right->frequency);
internalNode->left = left;
internalNode->right = right;
pq.push(internalNode);
}
root = pq.top();
}
// 打印霍夫曼编码
void printCodes() {
vector<int> code;
printCodesHelper(root, code, 0);
}
private:
// 递归辅助函数,打印霍夫曼编码
void printCodesHelper(HuffmanNode* node, vector<int>& code, int length) {
if (node->left == nullptr && node->right == nullptr) {
cout << "Character: " << node->data << ", Code: ";
for (int i = 0; i < length; ++i) {
cout << code[i];
}
cout << endl;
} else {
if (node->left != nullptr) {
code[length] = 0;
printCodesHelper(node->left, code, length + 1);
}
if (node->right != nullptr) {
code[length] = 1;
printCodesHelper(node->right, code, length + 1);
}
}
}
};
// 示例用法
int main() {
vector<int> frequencies = {5, 9, 12, 13, 16, 45};
vector<char> data = {'a', 'b', 'c', 'd', 'e', 'f'};
HuffmanTree huffmanTree(frequencies, data);
huffmanTree.printCodes();
// 在实际问题中,根据需要执行一系列的操作
return 0;
}
八、图论
1. 图论部分简介
图论是数学的一个分支,研究图的性质和图之间的关系。图由节点(顶点)和边组成,它可以用于描述各种实际问题,如社交网络、电子电路、交通网络等。图论的重要概念包括顶点、边、度、路径、环等。
2. 图论相关概念
- 顶点(Vertex): 图的基本元素,通常表示为 VVV。
- 边(Edge): 连接两个顶点的线段,通常表示为 EEE。
- 度(Degree): 一个顶点相连的边数。
- 路径(Path): 顶点之间的连接序列,路径上的边数可能为零。
- 环(Cycle): 起点和终点相同的路径。
3. 图的存储
图可以通过邻接矩阵和邻接表两种方式进行存储。
- 邻接矩阵: 用二维数组表示顶点之间的连接关系。
- 邻接表: 每个顶点的连接关系使用链表表示。
4. 最短路径
4.1. Dijkstra算法
#include <vector>
#include <queue>
using namespace std;
typedef pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 5;
vector<pii> graph[N];
vector<int> dist(N, INF);
void dijkstra(int start)
{
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
dist[start] = 0;
while (!pq.empty())
{
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > dist[u])
continue;
for (auto edge : graph[u])
{
int v = edge.second;
int w = edge.first;
if (dist[u] + w < dist[v])
{
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
}
4.2. Bellman-Ford算法
#include <vector>
using namespace std;
typedef pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 5;
vector<pii> edges(N);
vector<int> dist(N, INF);
void bellmanFord(int start, int n)
{
dist[start] = 0;
for (int i = 1; i <= n - 1; i++)
{
for (auto edge : edges)
{
int u = edge.first;
int v = edge.second;
int w = edge.weight;
if (dist[u] + w < dist[v])
{
dist[v] = dist[u] + w;
}
}
}
}
4.3. Floyd-Warshall算法
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 305;
vector<vector<int>> dist(N, vector<int>(N, INF));
void floydWarshall(int n)
{
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (dist[i][k] != INF && dist[k][j] != INF && dist[i][k] + dist[k][j] < dist[i][j])
{
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
}
5. 最小生成树
5.1 Prim算法
#include <vector>
#include <queue>
using namespace std;
typedef pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 5;
vector<pii> graph[N];
vector<bool> visited(N, false);
int prim(int start)
{
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
int minimumCost = 0;
while (!pq.empty())
{
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (visited[u])
continue;
visited[u] = true;
minimumCost += d;
for (auto edge : graph[u])
{
int v = edge.second;
int w = edge.first;
if (!visited[v])
{
pq.push({w, v});
}
}
}
return minimumCost;
}
5.2. Kruskal算法
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, pii> Edge;
const int N = 1e5 + 5;
vector<Edge> edges;
vector<int> parent(N);
int find(int x)
{
return parent[x] == x ? x : parent[x] = find(parent[x]);
}
void unite(int x, int y)
{
x = find(x);
y = find(y);
if (x != y)
{
parent[x] = y;
}
}
int kruskal(int n)
{
sort(edges.begin(), edges.end());
int minimumCost = 0;
for (auto edge : edges)
{
int w = edge.first;
int u = edge.second.first;
int v = edge.second.second;
if (find(u) != find(v))
{
unite(u, v);
minimumCost += w;
}
}
return minimumCost;
}
6. 树上问题
#include <iostream>
#include <vector>
using namespace std;
/**
* 求解树的直径
* @param u 当前节点
* @param parent u的父节点
* @param graph 树的邻接表表示
* @param diameter 树的直径
*/
void findDiameter(int u, int parent, vector<vector<int>>& graph, int& diameter) {
int maxDepth1 = 0, maxDepth2 = 0;
for (int v : graph[u]) {
if (v != parent) {
int depth = 1;
findDiameter(v, u, graph, diameter, depth);
if (depth > maxDepth1) {
maxDepth2 = maxDepth1;
maxDepth1 = depth;
} else if (depth > maxDepth2) {
maxDepth2 = depth;
}
}
}
diameter = max(diameter, maxDepth1 + maxDepth2);
}
// 使用示例:
// vector<vector<int>> tree = {{1}, {0, 2, 3}, {1}, {1}};
// int diameter = 0;
// findDiameter(0, -1, tree, diameter);
// cout << "树的直径为:" << diameter << endl;
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V)O(V)O(V),存储顶点的访问状态。
7. 矩阵树定理
#include <iostream>
#include <vector>
using namespace std;
/**
* 计算图的度数矩阵
* @param graph 图的邻接矩阵表示
* @return 度数矩阵
*/
vector<vector<int>> calculateDegreeMatrix(const vector<vector<int>>& graph) {
int n = graph.size();
vector<vector<int>> degreeMatrix(n, vector<int>(n, 0));
for (int i = 0; i < n; i++) {
int degree = 0;
for (int j = 0; j < n; j++) {
degree += graph[i][j];
}
degreeMatrix[i][i] = degree;
}
return degreeMatrix;
}
// 使用示例:
// vector<vector<int>> adjacencyMatrix = {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}};
// vector<vector<int>> degreeMatrix = calculateDegreeMatrix(adjacencyMatrix);
时间复杂度: O(V2)O(V^2)O(V2),其中 VVV 为顶点数。
空间复杂度: O(V2)O(V^2)O(V2),存储度数矩阵。
8. 有向无环图
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
/**
* 判断有向图是否是无环图(DAG)
* @param graph 有向图的邻接表表示
* @return 是否是无环图
*/
bool isDAG(const vector<vector<int>>& graph) {
int n = graph.size();
vector<int> indegree(n, 0);
for (int i = 0; i < n; i++) {
for (int j : graph[i]) {
indegree[j]++;
}
}
queue<int> q;
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}
int visited = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
visited++;
for (int v : graph[u]) {
indegree[v]--;
if (indegree[v] == 0) {
q.push(v);
}
}
}
return visited == n;
}
// 使用示例:
// vector<vector<int>> dag = {{1, 2}, {3}, {3}, {}};
// bool result = isDAG(dag);
// cout << "是否是无环图:" << (result ? "是" : "否") << endl;
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储入度和队列。
9. 拓扑排序
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
/**
* 拓扑排序
* @param graph 有向图的邻接表表示
* @param result 存储拓扑排序的结果
* @return 是否存在拓扑排序
*/
bool topologicalSort(const vector<vector<int>>& graph, vector<int>& result) {
int n = graph.size();
vector<int> indegree(n, 0);
for (int i = 0; i < n; i++) {
for (int j : graph[i]) {
indegree[j]++;
}
}
queue<int> q;
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
result.push_back(u);
for (int v : graph[u]) {
indegree[v]--;
if (indegree[v] == 0) {
q.push(v);
}
}
}
return result.size() == n;
}
// 使用示例:
// vector<vector<int>> dag = {{1, 2}, {3}, {3}, {}};
// vector<int> result;
// bool hasTopologicalOrder = topologicalSort(dag, result);
// cout << "拓扑排序是否存在:" << (hasTopologicalOrder ? "是" : "否") << endl;
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储入度和队列。
11. 斯坦纳树
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 计算图的斯坦纳树
* @param n 图的顶点数
* @param terminals 斯坦纳树的终端节点集合
* @param graph 图的邻接矩阵表示
* @return 斯坦纳树的权重
*/
int steinerTree(int n, vector<int>& terminals, vector<vector<int>>& graph) {
vector<vector<int>> dp(1 << terminals.size(), vector<int>(n, INT_MAX));
for (int terminal : terminals) {
dp[1 << distance(terminals.begin(), find(terminals.begin(), terminals.end(), terminal))][terminal] = 0;
}
for (int mask = 1; mask < (1 << terminals.size()); mask++) {
for (int u = 0; u < n; u++) {
for (int submask = mask; submask > 0; submask = (submask - 1) & mask) {
dp[mask][u] = min(dp[mask][u], dp[submask][u] + dp[mask ^ submask][u]);
for (int v = 0; v < n; v++) {
dp[mask][u] = min(dp[mask][u], dp[submask][u] + dp[mask ^ submask][v] + graph[u][v]);
}
}
}
}
int result = INT_MAX;
for (int u = 0; u < n; u++) {
result = min(result, dp[(1 << terminals.size()) - 1][u]);
}
return result;
}
// 使用示例:
// vector<int> terminals = {0, 2, 4};
// vector<vector<int>> graph = {{0, 2, 4}, {2, 0, 1}, {4, 1, 0}};
// int weight = steinerTree(5, terminals, graph);
时间复杂度: O(2k⋅k⋅n2)O(2^k \cdot k \cdot n^2)O(2k⋅k⋅n2),其中 kkk 为终端节点数,nnn 为顶点数。
空间复杂度: O(2k⋅n)O(2^k \cdot n)O(2k⋅n),存储状态转移数组。
12. 最小树形图
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 计算图的最小树形图
* @param root 树根
* @param n 图的顶点数
* @param graph 图的邻接矩阵表示
* @return 最小树形图的权重
*/
int chuLiuEdmonds(int root, int n, vector<vector<int>>& graph) {
int result = 0;
vector<int> minInEdge(n, INT_MAX);
vector<int> minInEdgeIdx(n, -1);
for (int u = 0; u < n; u++) {
if (u != root && graph[u][root] < minInEdge[u]) {
minInEdge[u] = graph[u][root];
minInEdgeIdx[u] = root;
}
}
for (int u = 0; u < n; u++) {
if (u == root) {
continue;
}
vector<bool> visited(n, false);
int v = u;
while (v != root && !visited[v] && minInEdgeIdx[v] != -1) {
visited[v] = true;
result += minInEdge[v];
v = minInEdgeIdx[v];
}
if (v != root && !visited[v]) {
for (int w = minInEdgeIdx[v]; w != v; w = minInEdgeIdx[w]) {
minInEdgeIdx[w] = v;
}
}
}
return result;
}
// 使用示例:
// vector<vector<int>> graph = {{0, 2, 4}, {2, 0, 1}, {4, 1, 0}};
// int weight = chuLiuEdmonds(0, 3, graph);
时间复杂度: O(V3)O(V^3)O(V3),其中 VVV 为顶点数。
空间复杂度: O(V)O(V)O(V),存储最小入边权重和最小入边的顶点。
13. 最小直径生成树
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 计算图的最小直径生成树
* @param n 图的顶点数
* @param graph 图的邻接矩阵表示
* @return 最小直径生成树的权重
*/
int minimumDiameterSpanningTree(int n, vector<vector<int>>& graph) {
int result = INT_MAX;
for (int k = 0; k < n; k++) {
vector<int> maxEdge(n, INT_MIN);
for (int i = 0; i < n; i++) {
if (i != k) {
maxEdge[i] = max(maxEdge[i], graph[k][i]);
}
}
for (int i = 0; i < n; i++) {
if (i != k) {
for (int j = i + 1; j < n; j++) {
if (j != k) {
result = min(result, max(maxEdge[i], maxEdge[j]) + graph[i][j]);
}
}
}
}
}
return result;
}
// 使用示例:
// vector<vector<int>> graph = {{0, 2, 4}, {2, 0, 1}, {4, 1, 0}};
// int weight = minimumDiameterSpanningTree(3, graph);
时间复杂度: O(V3)O(V^3)O(V3),其中 VVV 为顶点数。
空间复杂度: O(V)O(V)O(V),存储最大边权。
14. 最短路
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits>
using namespace std;
/**
* 计算图的单源最短路
* @param start 起始节点
* @param n 图的顶点数
* @param graph 图的邻接矩阵表示
* @return 单源最短路的距离数组
*/
vector<int> dijkstra(int start, int n, vector<vector<int>>& graph) {
vector<int> distance(n, numeric_limits<int>::max());
vector<bool> visited(n, false);
distance[start] = 0;
for (int i = 0; i < n - 1; i++) {
int u = -1;
for (int j = 0; j < n; j++) {
if (!visited[j] && (u == -1 || distance[j] < distance[u])) {
u = j;
}
}
visited[u] = true;
for (int v = 0; v < n; v++) {
if (!visited[v] && graph[u][v] != numeric_limits<int>::max() && distance[u] + graph[u][v] < distance[v]) {
distance[v] = distance[u] + graph[u][v];
}
}
}
return distance;
}
// 使用示例:
// vector<vector<int>> graph = {{0, 2, 4}, {2, 0, 1}, {4, 1, 0}};
// vector<int> distance = dijkstra(0, 3, graph);
时间复杂度: O(V2)O(V^2)O(V2),其中 VVV 为顶点数。
空间复杂度: O(V)O(V)O(V),存储距离数组和访问状态。
15. 拆点
#include <iostream>
#include <vector>
using namespace std;
/**
* 图的拆点
* @param n 原图的顶点数
* @param graph 原图的邻接矩阵表示
* @return 拆点后的新图的邻接矩阵表示
*/
vector<vector<int>> splitVertices(int n, vector<vector<int>>& graph) {
int newVertices = n * 2;
vector<vector<int>> newGraph(newVertices, vector<int>(newVertices, numeric_limits<int>::max()));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (graph[i][j] != numeric_limits<int>::max()) {
newGraph[i][n + j] = graph[i][j];
}
}
}
return newGraph;
}
// 使用示例:
// vector<vector<int>> graph = {{0, 2, 4}, {2, 0, 1}, {4, 1, 0}};
// vector<vector<int>> newGraph = splitVertices(3, graph);
时间复杂度: O(V2)O(V^2)O(V2),其中 VVV 为顶点数。
空间复杂度: O(V2)O(V^2)O(V2),存储新图的邻接矩阵表示。
16. 差分约束
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct Constraint {
int from, to, weight;
Constraint(int from, int to, int weight) : from(from), to(to), weight(weight) {}
};
/**
* 差分约束系统求解
* @param n 变量数
* @param constraints 差分约束集合
* @return 变量的取值数组,如果不存在合法解返回空数组
*/
vector<int> differentialConstraints(int n, vector<Constraint>& constraints) {
vector<int> distance(n, 0);
vector<int> indegree(n, 0);
vector<vector<pair<int, int>>> graph(n);
for (const Constraint& constraint : constraints) {
graph[constraint.from].emplace_back(constraint.to, constraint.weight);
indegree[constraint.to]++;
}
queue<int> q;
for (int i = 0; i < n; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (const pair<int, int>& neighbor : graph[u]) {
int v = neighbor.first;
int weight = neighbor.second;
distance[v] = max(distance[v], distance[u] + weight);
indegree[v]--;
if (indegree[v] == 0) {
q.push(v);
}
}
}
for (int i = 0; i < n; i++) {
if (indegree[i] > 0) {
// 存在环,无解
return {};
}
}
return distance;
}
// 使用示例:
// vector<Constraint> constraints = {{0, 1, 5}, {1, 2, 3}, {2, 0, -8}};
// vector<int> result = differentialConstraints(3, constraints);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储图的邻接表和入度数组。
17. k 短路
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
using namespace std;
struct Edge {
int to, weight;
Edge(int to, int weight) : to(to), weight(weight) {}
};
/**
* 计算图的k短路
* @param start 起始节点
* @param target 目标节点
* @param k 需要计算的路径数量
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 前k短路的路径长度
*/
vector<int> kShortestPaths(int start, int target, int k, int n, vector<vector<Edge>>& graph) {
vector<int> distance(n, numeric_limits<int>::max());
vector<vector<int>> paths(n);
vector<int> count(n, 0);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.push({0, start});
distance[start] = 0;
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > distance[u]) {
continue;
}
for (const Edge& edge : graph[u]) {
int v = edge.to;
int w = edge.weight;
if (distance[u] + w < distance[v]) {
distance[v] = distance[u] + w;
pq.push({distance[v], v});
paths[v].clear();
count[v] = 0;
}
if (distance[u] + w == distance[v]) {
paths[v].push_back(u);
count[v]++;
}
}
}
vector<int> result;
function<void(int, vector<int>&)> dfs = [&](int u, vector<int>& currentPath) {
currentPath.push_back(u);
if (u == start) {
result.push_back(distance[target]);
} else {
for (int v : paths[u]) {
dfs(v, currentPath);
}
}
currentPath.pop_back();
};
vector<int> currentPath;
dfs(target, currentPath);
sort(result.begin(), result.end());
return result.size() >= k ? vector<int>(result.begin(), result.begin() + k) : vector<int>();
}
// 使用示例:
// vector<vector<Edge>> graph = {{{1, 5}, {2, 2}}, {{3, 4}}, {{3, 7}}, {}};
// vector<int> result = kShortestPaths(0, 3, 2, 4, graph);
时间复杂度: O((V+E)logV)O((V + E) \log V)O((V+E)logV),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储图的邻接表和路径信息。
18. 同余最短路
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
using namespace std;
struct Edge {
int to, weight;
Edge(int to, int weight) : to(to), weight(weight) {}
};
/**
* 计算图的同余最短路
* @param start 起始节点
* @param target 目标节点
* @param modulo 同余模数
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 同余最短路的长度
*/
int congruenceShortestPath(int start, int target, int modulo, int n, vector<vector<Edge>>& graph) {
vector<int> distance(n, numeric_limits<int>::max());
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.push({0, start});
distance[start] = 0;
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > distance[u]) {
continue;
}
for (const Edge& edge : graph[u]) {
int v = edge.to;
int w = edge.weight;
if ((distance[u] + w) % modulo < distance[v]) {
distance[v] = (distance[u] + w) % modulo;
pq.push({distance[v], v});
}
}
}
return distance[target];
}
// 使用示例:
// vector<vector<Edge>> graph = {{{1, 5}, {2, 2}}, {{3, 4}}, {{3, 7}}, {}};
// int result = congruenceShortestPath(0, 3, 5, 4, graph);
时间复杂度: O(V+ElogV)O(V + E \log V)O(V+ElogV),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储图的邻接表和距离数组。
19. 连通性相关
#include <iostream>
#include <vector>
using namespace std;
/**
* 计算图的连通分量数量
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 连通分量的数量
*/
int countConnectedComponents(int n, vector<vector<int>>& graph) {
vector<int> component(n, -1);
int components = 0;
function<void(int)> dfs = [&](int u) {
component[u] = components;
for (int v : graph[u]) {
if (component[v] == -1) {
dfs(v);
}
}
};
for (int i = 0; i < n; i++) {
if (component[i] == -1) {
dfs(i);
components++;
}
}
return components;
}
// 使用示例:
// vector<vector<int>> graph = {{1}, {0, 2}, {2, 3}, {3}};
// int result = countConnectedComponents(4, graph);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V)O(V)O(V),存储连通分量信息。
20. 环计数问题
#include <iostream>
#include <vector>
using namespace std;
/**
* 计算图中的环的数量
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 图中的环的数量
*/
int countCycles(int n, vector<vector<int>>& graph) {
vector<bool> visited(n, false);
int cycles = 0;
function<bool(int, int)> dfs = [&](int u, int parent) {
visited[u] = true;
for (int v : graph[u]) {
if (!visited[v]) {
if (dfs(v, u)) {
cycles++;
}
} else if (v != parent) {
return true;
}
}
visited[u] = false;
return false;
};
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(i, -1);
}
}
return cycles / 2;
}
// 使用示例:
// vector<vector<int>> graph = {{1}, {0, 2}, {2, 3}, {3}};
// int result = countCycles(4, graph);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V)O(V)O(V),存储访问状态。
21. 2-SAT
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
/**
* 判断图是否是2-SAT可满足的
* @param n 图的顶点数
* @param clauses 图的边集合,每个元素是一个二元组 (a, b),表示存在一条有向边 a->b
* @return 如果可满足返回true,否则返回false
*/
bool is2SAT(int n, const vector<pair<int, int>>& clauses) {
vector<vector<int>> graph(2 * n);
vector<vector<int>> reverseGraph(2 * n);
vector<bool> visited(2 * n, false);
stack<int> order;
for (const auto& clause : clauses) {
int a = clause.first;
int b = clause.second;
// 将 (a or b) 转化为 (!a -> b) and (!b -> a)
graph[(a + n) % (2 * n)].push_back(b);
graph[(b + n) % (2 * n)].push_back(a);
reverseGraph[b].push_back((a + n) % (2 * n));
reverseGraph[a].push_back((b + n) % (2 * n));
}
function<void(int)> dfs1 = [&](int u) {
visited[u] = true;
for (int v : graph[u]) {
if (!visited[v]) {
dfs1(v);
}
}
order.push(u);
};
function<void(int, vector<int>&)> dfs2 = [&](int u, vector<int>& component) {
visited[u] = true;
component.push_back(u);
for (int v : reverseGraph[u]) {
if (!visited[v]) {
dfs2(v, component);
}
}
};
for (int i = 0; i < 2 * n; i++) {
if (!visited[i]) {
dfs1(i);
}
}
fill(visited.begin(), visited.end(), false);
while (!order.empty()) {
int u = order.top();
order.pop();
if (!visited[u]) {
vector<int> component;
dfs2(u, component);
// 检查同一强连通分量是否包含 x 和 !x,如果是则不可满足
for (int vertex : component) {
if (find(component.begin(), component.end(), (vertex + n) % (2 * n)) != component.end()) {
return false;
}
}
}
}
return true;
}
// 使用示例:
// vector<pair<int, int>> clauses = {{0, 1}, {1, 2}, {2, 0}};
// bool result = is2SAT(3, clauses);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储图的邻接表和访问状态。
22. 欧拉图
#include <iostream>
#include <vector>
using namespace std;
/**
* 判断图是否为欧拉图
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 如果是欧拉图返回true,否则返回false
*/
bool isEulerianGraph(int n, const vector<vector<int>>& graph) {
vector<int> indegree(n, 0);
vector<int> outdegree(n, 0);
for (int u = 0; u < n; u++) {
for (int v : graph[u]) {
outdegree[u]++;
indegree[v]++;
}
}
int oddIndegreeCount = 0;
int oddOutdegreeCount = 0;
for (int i = 0; i < n; i++) {
if (indegree[i] != outdegree[i]) {
return false;
}
if (indegree[i] % 2 != 0) {
oddIndegreeCount++;
}
if (outdegree[i] % 2 != 0) {
oddOutdegreeCount++;
}
}
return oddIndegreeCount == 0 || (oddIndegreeCount == 2 && oddOutdegreeCount == 2);
}
// 使用示例:
// vector<vector<int>> graph = {{1}, {2}, {0}};
// bool result = isEulerianGraph(3, graph);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V)O(V)O(V),存储入度和出度数组。
23. 哈密顿图
#include <iostream>
#include <vector>
#include <bitset>
using namespace std;
/**
* 判断图是否为哈密顿图
* @param n 图的顶点数
* @param graph 图的邻接矩阵表示
* @return 如果是哈密顿图返回true,否则返回false
*/
bool isHamiltonianGraph(int n, const vector<vector<bool>>& graph) {
vector<bitset<20>> dp(1 << n, 0);
for (int i = 0; i < n; i++) {
dp[1 << i][i] = 1;
}
for (int mask = 0; mask < (1 << n); mask++) {
for (int u = 0; u < n; u++) {
if ((mask & (1 << u)) != 0) {
for (int v = 0; v < n; v++) {
if ((mask & (1 << v)) == 0 && graph[u][v]) {
dp[mask | (1 << v)][v] |= dp[mask][u];
}
}
}
}
}
for (int u = 0; u < n; u++) {
if (dp[(1 << n) - 1][u] && graph[u][0]) {
return true;
}
}
return false;
}
// 使用示例:
// vector<vector<bool>> graph = {{0, 1, 1, 1}, {1, 0, 1, 1}, {1, 1, 0, 1}, {1, 1, 1, 0}};
// bool result = isHamiltonianGraph(4, graph);
时间复杂度: O(2N⋅N2)O(2^N \cdot N^2)O(2N⋅N2),其中 NNN 为顶点数。
空间复杂度: O(2N⋅N)O(2^N \cdot N)O(2N⋅N),存储状态数组。
24. 二分图
#include <iostream>
#include <vector>
using namespace std;
/**
* 判断图是否为二分图
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 如果是二分图返回true,否则返回false
*/
bool isBipartiteGraph(int n, const vector<vector<int>>& graph) {
vector<int> color(n, -1);
function<bool(int, int)> dfs = [&](int u, int c) {
color[u] = c;
for (int v : graph[u]) {
if (color[v] == -1) {
if (!dfs(v, 1 - c)) {
return false;
}
} else if (color[v] == color[u]) {
return false;
}
}
return true;
};
for (int i = 0; i < n; i++) {
if (color[i] == -1 && !dfs(i, 0)) {
return false;
}
}
return true;
}
// 使用示例:
// vector<vector<int>> graph = {{1, 3}, {0, 2}, {1, 3}, {0, 2}};
// bool result = isBipartiteGraph(4, graph);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V)O(V)O(V),存储颜色数组。
25. 最小环
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
/**
* 寻找图中的最小环
* @param n 图的顶点数
* @param graph 图的邻接表表示
* @return 如果存在最小环返回环的长度,否则返回-1
*/
int findMinCycle(int n, const vector<vector<pair<int, int>>>& graph) {
int minCycle = INT_MAX;
for (int u = 0; u < n; u++) {
vector<int> distance(n, INT_MAX);
distance[u] = 0;
for (int i = 0; i < n - 1; i++) {
for (int v = 0; v < n; v++) {
for (const auto& edge : graph[v]) {
int w = edge.first;
int weight = edge.second;
if (distance[v] != INT_MAX && distance[v] + weight < distance[w]) {
distance[w] = distance[v] + weight;
}
}
}
}
for (int v = 0; v < n; v++) {
for (const auto& edge : graph[v]) {
int w = edge.first;
int weight = edge.second;
if (distance[v] != INT_MAX && distance[v] + weight < distance[w]) {
minCycle = min(minCycle, distance[v] + weight);
}
}
}
}
return minCycle == INT_MAX ? -1 : minCycle;
}
// 使用示例:
// vector<vector<pair<int, int>>> graph = {{{1, 2}}, {{2, -3}}, {{0, -2}}};
// int result = findMinCycle(3, graph);
时间复杂度: O(V⋅E)O(V \cdot E)O(V⋅E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V)O(V)O(V),存储距离数组。
26. 平面图
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
/**
* 判断图是否为平面图
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个二元组 (a, b),表示存在一条边 a->b
* @return 如果是平面图返回true,否则返回false
*/
bool isPlanarGraph(int n, const vector<pair<int, int>>& edges) {
// 尝试添加一条边到图中,如果导致图不再是平面图,则返回false
auto addEdge = [&](int u, int v, vector<vector<int>>& adjList) {
if (find(adjList[u].begin(), adjList[u].end(), v) == adjList[u].end()) {
adjList[u].push_back(v);
}
if (find(adjList[v].begin(), adjList[v].end(), u) == adjList[v].end()) {
adjList[v].push_back(u);
}
return true;
};
auto dfs = [&](int u, int parent, const vector<vector<int>>& adjList, vector<bool>& visited) {
visited[u] = true;
for (int v : adjList[u]) {
if (!visited[v] && !dfs(v, u, adjList, visited)) {
return false;
} else if (visited[v] && v != parent) {
return false;
}
}
return true;
};
auto isConnected = [&](const vector<vector<int>>& adjList) {
vector<bool> visited(n, false);
int start = find(visited.begin(), visited.end(), false) - visited.begin();
return dfs(start, -1, adjList, visited);
};
vector<vector<int>> adjList(n);
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
vector<vector<int>> adjListCopy = adjList;
if (!addEdge(u, v, adjListCopy)) {
return false;
}
if (!isConnected(adjListCopy)) {
return false;
}
adjList = adjListCopy;
}
// 判断是否满足Kuratowski定理,即图中不存在K5和K3,3两个子图
auto hasK5OrK33Subgraph = [&](const vector<vector<int>>& adjList) {
vector<int> degrees(n, 0);
for (int u = 0; u < n; u++) {
degrees[u] = adjList[u].size();
}
for (int u = 0; u < n; u++) {
for (int v : adjList[u]) {
if (degrees[u] >= 3 && degrees[v] >= 3) {
vector<bool> visited(n, false);
visited[u] = true;
visited[v] = true;
for (int w : adjList[u]) {
if (w != v) {
visited[w] = true;
}
}
for (int w : adjList[v]) {
if (w != u) {
visited[w] = true;
}
}
int count = count(visited.begin(), visited.end(), true);
if (count == 5 || count == 6) {
return true;
}
}
}
}
return false;
};
return !hasK5OrK33Subgraph(adjList);
}
// 使用示例:
// vector<pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}};
// bool result = isPlanarGraph(3, edges);
时间复杂度: O(V⋅E)O(V \cdot E)O(V⋅E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储邻接表和度数数组。
27. 图的着色
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 判断图是否可以使用k种颜色进行着色
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个二元组 (a, b),表示存在一条边 a->b
* @param k 颜色的种类数
* @return 如果可以着色返回true,否则返回false
*/
bool isGraphColorable(int n, const vector<pair<int, int>>& edges, int k) {
vector<vector<int>> adjList(n);
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
adjList[u].push_back(v);
adjList[v].push_back(u);
}
vector<int> color(n, -1);
function<bool(int, int)> dfs = [&](int u, int c) {
color[u] = c;
for (int v : adjList[u]) {
if (color[v] == -1) {
if (!dfs(v, (c + 1) % k)) {
return false;
}
} else if (color[v] == color[u]) {
return false;
}
}
return true;
};
for (int i = 0; i < n; i++) {
if (color[i] == -1 && !dfs(i, 0)) {
return false;
}
}
return true;
}
// 使用示例:
// vector<pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}};
// bool result = isGraphColorable(3, edges, 3);
时间复杂度: O(V+E)O(V + E)O(V+E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储邻接表和颜色数组。
28. 网络流
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int INF = 1e9;
/**
* Dinic 网络流算法
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个三元组 (a, b, capacity),表示存在一条从 a 到 b 容量为 capacity 的边
* @param source 源点
* @param sink 汇点
* @return 最大流的值
*/
int dinicMaxFlow(int n, const vector<tuple<int, int, int>>& edges, int source, int sink) {
vector<vector<int>> capacity(n, vector<int>(n, 0));
for (const auto& edge : edges) {
int u = get<0>(edge);
int v = get<1>(edge);
int cap = get<2>(edge);
capacity[u][v] += cap;
}
int maxFlow = 0;
while (true) {
vector<int> level(n, -1);
queue<int> q;
q.push(source);
level[source] = 0;
while (!q.empty() && level[sink] == -1) {
int u = q.front();
q.pop();
for (int v = 0; v < n; v++) {
if (level[v] == -1 && capacity[u][v] > 0) {
level[v] = level[u] + 1;
q.push(v);
}
}
}
if (level[sink] == -1) {
break;
}
while (true) {
vector<int> ptr(n, 0);
queue<int> q;
q.push(source);
ptr[source] = -1;
while (!q.empty() && ptr[sink] == 0) {
int u = q.front();
q.pop();
for (int v = 0; v < n; v++) {
if (ptr[v] == 0 && level[u] + 1 == level[v] && capacity[u][v] > 0) {
ptr[v] = u;
q.push(v);
}
}
}
if (ptr[sink] == 0) {
break;
}
int aug = INF;
for (int u = sink; ptr[u] > 0; u = ptr[u]) {
aug = min(aug, capacity[ptr[u]][u]);
}
for (int u = sink; ptr[u] > 0; u = ptr[u]) {
capacity[ptr[u]][u] -= aug;
capacity[u][ptr[u]] += aug;
}
maxFlow += aug;
}
}
return maxFlow;
}
// 使用示例:
// vector<tuple<int, int, int>> edges = {{0, 1, 10}, {1, 2, 5}, {0, 2, 10}};
// int result = dinicMaxFlow(3, edges, 0, 2);
时间复杂度: O(V2⋅E)O(V^2 \cdot E)O(V2⋅E),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V2)O(V^2)O(V2),存储容量矩阵。
29. Stoer–Wagner 算法
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* Stoer-Wagner 最小割算法
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个三元组 (a, b, capacity),表示存在一条从 a 到 b 容量为 capacity 的边
* @return 最小割的值
*/
int stoerWagnerMinCut(int n, const vector<vector<int>>& graph) {
vector<int> v(n); // v[i] 表示第i个节点在当前图中的标号
vector<int> weight(n, 0); // weight[i] 表示节点i与其它节点的连边权值之和
for (int i = 0; i < n; i++) {
v[i] = i;
}
int minCut = INT_MAX;
for (int phase = n; phase > 1; phase--) {
vector<int> prev = weight;
vector<bool> isIncluded(n, false);
int u = 0, prevU = 0;
for (int i = 0; i < phase; i++) {
int maxWeight = 0;
for (int j = 0; j < n; j++) {
if (!isIncluded[v[j]]) {
weight[v[j]] += graph[prevU][v[j]];
if (weight[v[j]] > weight[maxWeight]) {
maxWeight = v[j];
}
}
}
isIncluded[maxWeight] = true;
if (i == phase - 1) {
for (int j = 0; j < n; j++) {
graph[prevU][v[j]] += graph[v[j]][maxWeight];
graph[v[j]][prevU] += graph[v[j]][maxWeight];
}
v.erase(remove(v.begin(), v.end(), maxWeight), v.end());
minCut = min(minCut, weight[maxWeight]);
} else {
prevU = maxWeight;
}
}
weight = prev;
}
return minCut;
}
// 使用示例:
// vector<vector<int>> graph = {{0, 2, 2, 3}, {2, 0, 5, 1}, {2, 5, 0, 4}, {3, 1, 4, 0}};
// int result = stoerWagnerMinCut(4, graph);
时间复杂度: O(V3)O(V^3)O(V3),其中 VVV 为顶点数。
空间复杂度: O(V2)O(V^2)O(V2),存储图的邻接矩阵。
30. 图的匹配
#include <iostream>
#include <vector>
using namespace std;
/**
* 二分图的最大匹配算法(匈牙利算法)
* @param n 左侧顶点数
* @param m 右侧顶点数
* @param edges 匹配边的集合,每个元素是一个二元组 (a, b),表示存在一条从 a 到 b 的匹配边
* @return 最大匹配的边数
*/
int hungarianMaxMatching(int n, int m, const vector<pair<int, int>>& edges) {
vector<vector<int>> adjList(n);
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
adjList[u].push_back(v);
}
vector<int> matchRight(m, -1);
vector<bool> visited(n, false);
function<bool(int)> augment = [&](int u) {
if (visited[u]) {
return false;
}
visited[u] = true;
for (int v : adjList[u]) {
if (matchRight[v] == -1 || augment(matchRight[v])) {
matchRight[v] = u;
return true;
}
}
return false;
};
int maxMatching = 0;
for (int i = 0; i < n; i++) {
fill(visited.begin(), visited.end(), false);
if (augment(i)) {
maxMatching++;
}
}
return maxMatching;
}
// 使用示例:
// vector<pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}};
// int result = hungarianMaxMatching(3, 3, edges);
时间复杂度: O(VE)O(VE)O(VE),其中 VVV 为顶点数,EEE 为边数。
空间复杂度: O(V+E)O(V + E)O(V+E),存储邻接表和匹配数组。
31. Prüfer 序列
#include <iostream>
#include <vector>
#include <set>
using namespace std;
/**
* 根据 Prüfer 序列构造无根树
* @param n 无根树的节点数
* @param pruferSeq Prüfer 序列
* @return 无根树的边集合,每个元素是一个二元组 (a, b),表示存在一条边 a->b
*/
vector<pair<int, int>> constructTreeFromPruefer(int n, const vector<int>& pruferSeq) {
set<int> leaves;
vector<int> degree(n + 2, 1);
for (int i = 1; i <= n; i++) {
leaves.insert(i);
}
for (int i : pruferSeq) {
degree[i]++;
}
vector<pair<int, int>> edges;
for (int i : pruferSeq) {
auto leaf = leaves.begin();
edges.emplace_back(i, *leaf);
leaves.erase(leaf);
if (--degree[i] == 0) {
leaves.insert(i);
}
}
int u = *leaves.begin();
int v = *leaves.rbegin();
edges.emplace_back(u, v);
return edges;
}
// 使用示例:
// vector<int> pruferSeq = {3, 3, 3, 4, 4};
// vector<pair<int, int>> result = constructTreeFromPruefer(5, pruferSeq);
时间复杂度: O(NlogN)O(N \log N)O(NlogN),其中 NNN 为节点数。
空间复杂度: O(N)O(N)O(N),存储度数和边集合。
32. LGV 引理
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 通过 Line Graph Vector Lemma 计算图的 Hamiltonian Cycle 的个数
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个二元组 (a, b),表示存在一条边 a->b
* @return Hamiltonian Cycle 的个数
*/
long long lineGraphVectorLemma(int n, const vector<pair<int, int>>& edges) {
vector<vector<int>> adjList(n);
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
adjList[u].push_back(v);
adjList[v].push_back(u);
}
vector<long long> dp(1 << n, 0);
dp[0] = 1;
for (int mask = 1; mask < (1 << n); mask++) {
int u = __builtin_ctz(mask);
for (int v : adjList[u]) {
if ((mask & (1 << v)) && !(mask & (1 << (v + 1)))) {
dp[mask | (1 << (v + 1))] += dp[mask];
}
}
}
return dp[(1 << n) - 1];
}
// 使用示例:
// vector<pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}};
// long long result = lineGraphVectorLemma(3, edges);
时间复杂度: O(2N⋅N)O(2^N \cdot N)O(2N⋅N),其中 NNN 为顶点数。
空间复杂度: O(2N)O(2^N)O(2N),存储动态规划数组。
33. 弦图
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 判断一个图是否为弦图
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个二元组 (a, b),表示存在一条边 a->b
* @return 如果是弦图返回 true,否则返回 false
*/
bool isChordalGraph(int n, const vector<pair<int, int>>& edges) {
vector<vector<int>> adjList(n);
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
adjList[u].push_back(v);
adjList[v].push_back(u);
}
vector<int> label(n);
vector<int> order(n);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), [&](int a, int b) { return adjList[a].size() > adjList[b].size(); });
for (int i = 0; i < n; i++) {
label[order[i]] = i;
}
for (int u = 0; u < n; u++) {
sort(adjList[u].begin(), adjList[u].end(), [&](int a, int b) { return label[a] < label[b]; });
}
vector<int> pos(n, -1);
for (int i = 0; i < n; i++) {
int u = order[i];
pos[u] = i;
for (int v : adjList[u]) {
if (label[v] > label[u]) {
continue;
}
for (int w : adjList[v]) {
if (label[w] > label[u]) {
continue;
}
if (pos[w] == -1) {
return false;
}
}
}
}
return true;
}
// 使用示例:
// vector<pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}};
// bool result = isChordalGraph(3, edges);
时间复杂度: O(N2logN)O(N^2 \log N)O(N2logN),其中 NNN 为顶点数。
空间复杂度: O(N)O(N)O(N),存储标签和顺序数组。
34. 最大团搜索算法
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 最大团搜索算法
* @param n 图的顶点数
* @param edges 图的边集合,每个元素是一个二元组 (a, b),表示存在一条边 a->b
* @return 最大团的大小
*/
int maximumClique(int n, const vector<pair<int, int>>& edges) {
vector<vector<int>> adjList(n);
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
adjList[u].push_back(v);
adjList[v].push_back(u);
}
vector<int> order(n);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), [&](int a, int b) { return adjList[a].size() > adjList[b].size(); });
int maxCliqueSize = 0;
for (int i = 0; i < n; i++) {
vector<int> clique = {order[i]};
for (int j = i + 1; j < n; j++) {
if (find(adjList[order[i]].begin(), adjList[order[i]].end(), order[j]) != adjList[order[i]].end()) {
clique.push_back(order[j]);
}
}
vector<int> candidates;
for (int u : clique) {
for (int v : adjList[u]) {
if (find(clique.begin(), clique.end(), v) == clique.end()) {
candidates.push_back(v);
}
}
}
sort(candidates.begin(), candidates.end(), [&](int a, int b) { return adjList[a].size() > adjList[b].size(); });
for (int u : candidates) {
if (all_of(clique.begin(), clique.end(), [&](int v) { return find(adjList[u].begin(), adjList[u].end(), v) != adjList[u].end(); })) {
clique.push_back(u);
}
}
maxCliqueSize = max(maxCliqueSize, static_cast<int>(clique.size()));
}
return maxCliqueSize;
}
// 使用示例:
// vector<pair<int, int>> edges = {{0, 1}, {1, 2}, {2, 0}};
// int result = maximumClique(3, edges);
时间复杂度: O(2N2⋅N2)O(2^{\frac{N}{2}} \cdot N^2)O(22N⋅N2),其中 NNN 为顶点数。
空间复杂度: O(N)O(N)O(N),存储邻接表和团的数组。
九、数论
1. 质数
1.1 质数筛
埃氏筛
#include <bitset>
using namespace std;
const int N = 1e5;
bitset<N> pri;
void ehrlichSieve(int n)
{
for (int i = 2; i <= n / i; i++)
{
if (!pri[i])
{
for (int j = i * i; j <= n; j += i)
{
pri[j] = 1;
}
}
}
}
欧拉筛
#include <bitset>
using namespace std;
const int N = 1e5;
bitset<N> pri;
int primes[N];
int p = 0;
void EulerSieve(int n)
{
for (int i = 2; i <= n; i++)
{
if (!pri[i])
primes[++p] = i;
for (int j = 1; primes[j] * i <= n; ++j)
{
pri[primes[j] * i] = 1;
if (i % primes[j] == 0)
break;
}
}
}
2. 最大公约数与最小公倍数
2.1. 最大公约数(辗转相除法)
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
2.2. 最小公倍数
int lcm(int a, int b)
{
return a / gcd(a, b) * b;
}
3. 快速幂
long long quickPow(long long x, long long n, long long mod)
{
long long res = 1;
while (n)
{
if (n & 1)
res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
4. 求逆元
4.1. 快速幂法
int inv(int a, int p)
{
return quickPow(a, p - 2, p);
}
4.2. 扩展欧几里得法
int exgcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int tmp = x;
x = y;
y = tmp - a / b * y;
return d;
}
int inv(int a, int p)
{
int x, y;
exgcd(a, p, x, y);
return (x % p + p) % p;
}
5. 高精度计算
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class BigInteger {
public:
vector<int> digits;
// 构造函数,将字符串转为大整数
BigInteger(string str) {
for (int i = str.length() - 1; i >= 0; i--) {
digits.push_back(str[i] - '0');
}
}
// 大整数乘法
void multiply(int x) {
int carry = 0;
for (int i = 0; i < digits.size() || carry; i++) {
if (i == digits.size()) digits.push_back(0);
long long cur = digits[i] * 1ll * x + carry;
digits[i] = int(cur % 10);
carry = int(cur / 10);
}
while (digits.size() > 1 && digits.back() == 0) digits.pop_back();
}
// 打印大整数
void print() {
for (int i = digits.size() - 1; i >= 0; i--) {
cout << digits[i];
}
cout << endl;
}
};
// 使用示例:
// BigInteger a("123456789");
// a.multiply(987654321);
// a.print();
时间复杂度: O(max(N,logX))O(\max(N, \log{X}))O(max(N,logX)),其中 NNN 为大整数的位数,XXX 为乘法因子。
空间复杂度: O(N)O(N)O(N),存储大整数的位数。
6. 置换和排列
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 下一个排列
* @param nums 排列
* @return 是否存在下一个排列
*/
bool nextPermutation(vector<int>& nums) {
int i = nums.size() - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = nums.size() - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
swap(nums[i], nums[j]);
}
reverse(nums.begin() + i + 1, nums.end());
return i >= 0;
}
// 使用示例:
// vector<int> permutation = {1, 2, 3};
// do {
// // 处理当前排列
// for (int num : permutation) {
// cout << num << " ";
// }
// cout << endl;
// } while (nextPermutation(permutation));
时间复杂度: O(N)O(N)O(N),其中 NNN 为排列的长度。
空间复杂度: O(1)O(1)O(1)。
7. 弧度制与坐标系
#include <iostream>
#include <cmath>
using namespace std;
const double PI = acos(-1.0);
/**
* 角度转弧度
* @param degrees 角度
* @return 弧度
*/
double toRadians(double degrees) {
return degrees * PI / 180.0;
}
/**
* 弧度转角度
* @param radians 弧度
* @return 角度
*/
double toDegrees(double radians) {
return radians * 180.0 / PI;
}
// 使用示例:
// double degrees = 90.0;
// double radians = toRadians(degrees);
// cout << "90 degrees in radians: " << radians << endl;
时间复杂度: O(1)O(1)O(1)。
空间复杂度: O(1)O(1)O(1)。
8. 复数
#include <iostream>
#include <cmath>
using namespace std;
struct Complex {
double real, imag;
// 构造函数
Complex(double r, double i) : real(r), imag(i) {}
// 复数加法
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// 复数减法
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
// 复数乘法
Complex operator*(const Complex& other) const {
return Complex(real * other.real - imag * other.imag, real * other.imag + imag * other.real);
}
// 复数模长
double abs() const {
return sqrt(real * real + imag * imag);
}
};
// 使用示例:
// Complex a(1.0, 2.0);
// Complex b(2.0, 3.0);
// Complex c = a + b;
// cout << "Sum: " << c.real << " + " << c.imag << "i" << endl;
// cout << "Absolute value of c: " << c.abs() << endl;
时间复杂度: 复数操作的时间复杂度取决于底层数值类型。
空间复杂度: O(1)O(1)O(1)。