OneCoder Avatar
OneCodercoderli.com · 955 篇博文
八级

C++ 算法考级专栏

真题分析、矩阵探测、递归回溯与基础语法

🎨 视觉封面

【仙人掌图性质与乘法原理生成树计数】GESP八级 / CSP-S 题解:luogu-P17461 [GESP202609 八级] 生成树计数

📅 2026-09-15·✍️ OneCoder·计算中...·⏱️ 6 分钟
#GESP#C++#GESP八级#CSP-S#高级图论#仙人掌图#生成树#DFS#真题#2026年9月#GESP202609

CCF GESP 2026年9月认证(第十五次认证)C++ 八级试题,洛谷 P17461。本题严格遵循 CCF GESP 官方大纲规范,重点考察仙人掌图性质与乘法原理生成树计数。题目逻辑严密,模型典型,是深入理解与掌握信奥核心考点的经典范例。

P17461 [luogu-P17461 [GESP202609 八级] 生成树计数]

🔗 洛谷原题传送门P17461

题目要求

题目描述

无向连通图 GGnn 顶点 mm 边)。特殊性质:每条边至多属于一个简单环(仙人掌图)。 求 GG 的不同生成树的数量,答案对 998244353 取模。

输入格式

第一行两个正整数 n,mn, m。 接下来 mm 行每行两个整数 ui,viu_i, v_i

输出格式

输出一行,一个整数,表示生成树数量模 998244353 的值。

输入输出样例

样例输入 #1
TEXT
7 8
1 2
2 3
3 1
3 4
4 5
5 6
6 7
7 4
样例输出 #1
TEXT
12

说明/提示

1n,m1051 \le n, m \le 10^5。样例 1 中含有一个三元环和一个四元环,桥边必选,答案为 3×4=123 \times 4 = 12


题目分析与解题思路

  1. 仙人掌图生成树核心定理
    • 在仙人掌图(Cactus Graph)中,任意两个简单环不共享边。
    • 为了消除所有的环且保持全图连通,对于每个长度为 LL 的简单环,必须且只能恰好删去环上的一条边(有 LL 种独立的选择);
    • 不属于任何环的边(桥边,Bridge)必须保留在生成树中;
    • 由乘法原理,生成树总数严格等于所有简单环长度的乘积: Spanning Trees=CCyclesC(mod998244353)\text{Spanning Trees} = \prod_{C \in \text{Cycles}} |C| \pmod{998244353}
  2. 线性 DFS 环长提取: 在 DFS 搜索树中,每条反向返祖边(Back-edge)(u,v)(u, v) 与树边路径恰好构成一个简单环,其环长即为深度差加一:L=depth[u]depth[v]+1L = depth[u] - depth[v] + 1。 单次线性遍历即可求得所有环长,时间复杂度完美 O(n+m)\mathcal{O}(n + m)

完整参考代码 (C++11)

CPP
/**
 * Problem: luogu-P17461
 * Standard: C++11 (CCF GESP 官方大纲规范)
 * Author: OneCoder
 */

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 100005;
const int MOD = 998244353;

vector<int> adj[MAXN];
int depth_arr[MAXN];
bool visited[MAXN];
long long ans = 1;

void dfs(int u, int p, int d) {
    depth_arr[u] = d;
    visited[u] = true;
    for (int v : adj[u]) {
        if (v == p) continue;
        if (visited[v]) {
            if (depth_arr[v] < depth_arr[u]) {
                // 返祖边:找到一个简单环
                int cycle_len = depth_arr[u] - depth_arr[v] + 1;
                ans = (ans * cycle_len) % MOD;
            }
        } else {
            dfs(v, u, d + 1);
        }
    }
}

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

    int n, m;
    if (!(cin >> n >> m)) return 0;

    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    dfs(1, 0, 1);

    cout << ans << "\n";

    return 0;
}

考点归纳与备考建议

  1. 考纲匹配度:严格对标 CCF GESP 八级考纲重点,绝不超纲,注重基础算法与逻辑建模规范;
  2. 规范防范:所有代码严格以 C++11 标准编译运行,针对整数溢出、边界判断、空状态均做了详尽严整的防御性处理。
💡 OneCoder 资源指引

所有代码开源上传至 GitHub:yummy-code 仓库 · GESP 专题站:GESP WIKI

🤝 技术交流与答疑

欢迎加入:C++ GESP/CSP 考级答疑群(688906745)Java/Python交流群(982860385),点击可直接加群。

📚

猜你想读 · 相关文章推荐

OneCoder

OneCoder (lihongzheshuai)

一个中年人的自留地,记录学习 C++、GESP/NOI、Java、Python 与算法架构的心得体会。本站唯一网址:coderli.com

💬 读者留言与交流

0 条讨论
✨ 支持 Markdown 语法格式
还没有留言,快来成为第一个讨论者吧!