返回首页 C++ 模板

组合数学与图论进阶

排列组合、杨辉三角、倍增法、最小生成树、最短路——GESP 最高级算法

排列组合 杨辉三角 倍增 最小生成树 最短路 Dijkstra Kruskal GESP8
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
const int INF = 0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;

// ======================================================
//              第一部分:组合数学
// ======================================================

// ========== 1. 排列 A(n,k) = n! / (n-k)! ==========
ll permutation(int n, int k) {
    ll result = 1;
    for (int i = 0; i < k; i++) result *= (n - i);
    return result;
}

// ========== 2. 组合 C(n,k) = n! / (k!(n-k)!) ==========
// 杨辉三角递推
ll C[1010][1010];
void init_combinations(int n) {
    for (int i = 0; i <= n; i++) {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++) {
            C[i][j] = C[i-1][j-1] + C[i-1][j];
        }
    }
}

// ========== 3. 杨辉三角 ==========
void print_pascal(int n) {
    init_combinations(n);
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= i; j++) {
            cout << C[i][j] << " ";
        }
        cout << endl;
    }
}

// ======================================================
//              第二部分:并查集
// ======================================================

int fa[N];

void uf_init(int n) {
    for (int i = 1; i <= n; i++) fa[i] = i;
}

int uf_find(int x) {
    if (fa[x] != x) fa[x] = uf_find(fa[x]);  // 路径压缩
    return fa[x];
}

void uf_union(int a, int b) {
    fa[uf_find(a)] = uf_find(b);
}

bool uf_connected(int a, int b) {
    return uf_find(a) == uf_find(b);
}

// ======================================================
//              第三部分:最小生成树
// ======================================================

// ========== 4. Kruskal 算法 ==========
struct Edge {
    int u, v, w;
    bool operator<(const Edge &other) const {
        return w < other.w;
    }
};

ll kruskal(int n, vector<Edge> &edges) {
    sort(edges.begin(), edges.end());
    uf_init(n);

    ll totalWeight = 0;
    int edgeCount = 0;

    for (auto &e : edges) {
        if (!uf_connected(e.u, e.v)) {
            uf_union(e.u, e.v);
            totalWeight += e.w;
            edgeCount++;
            if (edgeCount == n - 1) break;
        }
    }

    if (edgeCount < n - 1) return -1;  // 图不连通
    return totalWeight;
}

// ========== 5. Prim 算法 ==========
ll prim(int n, vector<vector<pii>> &adj) {  // adj[u] = {v, w}
    vector<int> dist(n + 1, INF);
    vector<bool> inMST(n + 1, false);
    priority_queue<pii, vector<pii>, greater<pii>> pq;

    dist[1] = 0;
    pq.push({0, 1});

    ll totalWeight = 0;
    int nodeCount = 0;

    while (!pq.empty() && nodeCount < n) {
        auto [d, u] = pq.top();
        pq.pop();

        if (inMST[u]) continue;
        inMST[u] = true;
        totalWeight += d;
        nodeCount++;

        for (auto [v, w] : adj[u]) {
            if (!inMST[v] && w < dist[v]) {
                dist[v] = w;
                pq.push({w, v});
            }
        }
    }

    return totalWeight;
}

// ======================================================
//              第四部分:最短路
// ======================================================

// ========== 6. Dijkstra 算法(单源最短路)==========
// 适用于非负权图
ll dist_dijk[N];

void dijkstra(int start, int n, vector<vector<pii>> &adj) {
    fill(dist_dijk, dist_dijk + n + 1, INF);
    dist_dijk[start] = 0;

    priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<pair<ll,int>>> pq;
    pq.push({0, start});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        if (d > dist_dijk[u]) continue;  // 已有更短路径

        for (auto [v, w] : adj[u]) {
            if (dist_dijk[u] + w < dist_dijk[v]) {
                dist_dijk[v] = dist_dijk[u] + w;
                pq.push({dist_dijk[v], v});
            }
        }
    }
}

// ========== 7. Floyd 算法(多源最短路)==========
int floyd_dist[510][510];

void floyd(int n) {
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                floyd_dist[i][j] = min(floyd_dist[i][j],
                    floyd_dist[i][k] + floyd_dist[k][j]);
}

// ======================================================
//              第五部分:倍增法
// ======================================================

// ========== 8. 倍增法求 RMQ(区间最小值)==========
int st[N][20];  // st[i][j]: 从 i 开始 2^j 个元素的最小值
int log2_table[N];

void sparse_table_init(int a[], int n) {
    log2_table[1] = 0;
    for (int i = 2; i <= n; i++) log2_table[i] = log2_table[i/2] + 1;

    for (int i = 0; i < n; i++) st[i][0] = a[i];

    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 0; i + (1 << j) - 1 < n; i++) {
            st[i][j] = min(st[i][j-1], st[i + (1 << (j-1))][j-1]);
        }
    }
}

int range_min(int l, int r) {
    int k = log2_table[r - l + 1];
    return min(st[l][k], st[r - (1 << k) + 1][k]);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // 组合数学
    init_combinations(100);
    cout << "C(10,3) = " << C[10][3] << endl;  // 120

    // Dijkstra
    int n, m, start;
    cin >> n >> m >> start;
    vector<vector<pii>> adj(n + 1);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        adj[u].push_back({v, w});
        adj[v].push_back({u, w});  // 无向图
    }
    dijkstra(start, n, adj);
    for (int i = 1; i <= n; i++) {
        cout << i << ": " << dist_dijk[i] << endl;
    }

    return 0;
}

📖 要点说明

组合数学核心公式

公式 含义 示例
A(n,k) 从 n 选 k 个排列 A(5,3) = 60
C(n,k) 从 n 选 k 个组合 C(5,3) = 10
加法原理 分类计数,各类互斥 3条路去A + 2条路去B = 5种走法
乘法原理 分步计数,各步独立 3件上衣 × 2条裤子 = 6种搭配

两种最小生成树对比

对比项 Kruskal Prim
思路 边排序 + 并查集 点扩展 + 优先队列
时间 O(E log E) O(E log V)
适用 稀疏图 稠密图
关键 并查集判环 优先队列取最小

两种最短路对比

对比项 Dijkstra Floyd
类型 单源 多源
时间 O(E log V) O(V³)
负权 ❌ 不支持 ✅ 支持(但不能有负环)
适用 大图求单源 小图求全源

倍增法

⚠️ 常见错误