PART 1:题目描述
题目传送门:P17461 GESP202609 八级 生成树计数
题目描述
给定一张有 \(n\) 个顶点 \(m\) 条边的无向连通图 \(G\),顶点依次以 \(1,2,\ldots,n\) 编号。\(G\) 有以下特殊的性质:
- \(G\) 中的每条边至多属于一个简单环。
- \(G\) 中没有重边与自环。
简单环是指环中顶点互不相同,且不经过重复边的回路。
请你求出 \(G\) 的不同生成树的数量。两棵生成树不同,当且仅当存在一条边在其中一棵生成树中出现,而不在另一棵生成树中出现。
由于答案可能很大,你只要求出答案对 \(998244353\) 取模的结果。
输入格式
第一行,两个正整数 \(n,m\),分别表示 \(G\) 的顶点数与边数。
接下来 \(m\) 行,每行两个整数 \(u_i,v_i\),表示一条连接顶点 \(u_i,v_i\) 的无向边。
输出格式
输出一行,一个整数,表示 \(G\) 的不同生成树的数量对 \(998244353\) 取模的结果。
输入输出样例 #1
输入 #1
7 8
1 2
2 3
3 1
3 4
4 5
5 6
6 7
7 4
输出 #1
12
输入输出样例 #2
输入 #2
5 4
1 2
1 3
2 4
2 5
输出 #2
1
说明/提示
对于 \(40\%\) 的测试点,保证 \(1\le n\le 8\),\(1\le m\le 10\)。
对于 \(60\%\) 的测试点,保证 \(1\le n\le 2000\),\(1\le m\le 2000\)。
对于所有测试点,保证 \(1\le n\le 10^5\),\(1\le m\le 10^5\),\(1\le u_i,v_i\le n\)。
PART 2:解题思路
2.1题目大意
给定一张 \(n\) 个点、\(m\) 条边的无向连通图,保证每条边至多属于一个简单环(即仙人掌图)。求这张图的不同生成树数量,对 998244353 取模。
数据范围:\(1\le n,m\le 10^5\)
2.2什么是仙人掌图
仙人掌图就是一种长得像仙人掌的连通图:
图里所有的环(封闭的圈)互相绝不共用边,最多只在一个顶点处碰头,其余没成环的边就像是连接这些环的"刺"或"茎"。
2.3核心结论
结论:
\\boxed{ \\text{生成树数量} = \\prod_{\\text{每个简单环}}(\\text{环的长度})\\pmod{998244353}}
证明:
1. 桥边必须连通,如果桥边去掉,图就不连通了,无法形成生成树。
2. 每个环只能恰好删去一条边:
-
如果一条边都不删,生成树中就会包含环,不再是树。
-
如果删去\(≥2\)条边,就会有节点与图断开,无法形成生成树。
验证:
样例一:
图中存在两个环:
-
环\(1-2-3\),环的长度为3
-
环\(4-5-6-7\),环的长度为4
-
答案为\(3×4=12\)。
样例二:
图中无环,答案为1.
PART 3:算法实现找环
使用 \(DFS\) 遍历图,记录每个点的访问时间 vis\[u\] 和在 \(DFS\) 树中的深度 \(depu\)(根节点深度为 \(0\))。
如何判断是否找到了环?
对于节点\(u\):
-
如果遇到来时的边,跳过。
-
如果遇到未访问过的点,继续\(DFS\)。
-
如果遇到访问时间比当前节点早的节点,说明此时已经发现了一个环。
如何计算环的长度?
\(dep\) 记录的是该点在 DFS 树上的层数。回边 \(u−v\) 与 DFS \\(树上\\) u 到 \(v\) 的路径恰好构成了这个环。环上的边数就是它们在树上的深度差加上回边本身。用树的深度差值 dep[u] - dep[v] + 1
PART 4:代码实现
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
vector<pair<int,int> >e[100005];//存储节点编号与无向边编号
int n,m,cnt=0;//cnt记录访问时间
long long ans=1;//ans记录答案,初始为1
int vis[100005],dep[100005];//vis记录访问时间,dep记录节点深度
void dfs(int now,int fa,int d) {//now当前节点,fa来时边的编号,d当前深度
cnt++;//访问时间+1
vis[now]=cnt;//记录访问时间
dep[now]=d;//记录节点深度
for(auto i:e[now]) {//枚举当前节点可达的节点
if(i.second==fa) continue;//如果遇到来时的边,跳过。
if(!vis[i.first]) dfs(i.first,i.second,d+1);//如果遇到未访问的节点,继续DFS
else if(vis[i.first]<vis[now]){//如果发现环
int l=dep[now]-dep[i.first]+1;//计算环的长度
ans=ans*l%mod;//ans累乘
}
}
}
int main() {
cin>>n>>m;//输入节点数与边数
for(int i=1;i<=m;i++) {
int u,v;cin>>u>>v;//输入无向边
e[u].push_back({v,i});//无向边,双向存储
e[v].push_back({u,i});//i为边的编号
}
dfs(1,0,0);//初始在节点1
cout<<ans;//输出答案,记得mod 998244353
return 0;
}