算法竞赛模板———下(数据结构,图论,数论)

七、数据结构

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(2kkn2),其中 kkk 为终端节点数,nnn 为顶点数。

空间复杂度: O(2k⋅n)O(2^k \cdot n)O(2kn),存储状态转移数组。

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)log⁡V)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+Elog⁡V)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(2NN2),其中 NNN 为顶点数。

空间复杂度: O(2N⋅N)O(2^N \cdot N)O(2NN),存储状态数组。

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(VE),其中 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(VE),其中 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(V2E),其中 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(Nlog⁡N)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(2NN),其中 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(N2log⁡N)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(22NN2),其中 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,log⁡X))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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值