P6790 [SNOI2020] 生成树 题解

感觉很多题解都说的不是很清楚?如何将三操作与二操作合并起来一起处理好像都没有提到。(也有可能是我太菜了,看了半天才懂)

思路

考虑这个图一定是一个广义串并联图。为什么呢?
广义串并联图的定义是不存在一个子图包含四个点且这将四个点之间的路径看作边,这四个点和路径所构成的边构成一个完全图。

显然仙人掌不可能包含一个四个点的完全图,因为四个点的完全图包含了四个有共边的环。
那考虑这个四个点的完全图删去一条边后可不可能是仙人掌。由于删去一条边最多只能删去两个环,仍然有两个环是共边的。
而这里“边”变作路径是同理的,这里不再赘述。
因此仙人掌加上一条边仍然不存在一个四个点的完全子图,也就是这个图是广义串并联图。

然后我们就可以使用广义串并联图的经典操作:删 1 度点,缩 2 度点,叠合重边。
由于最后可以删的只剩一个点,因此考虑 DP 来求总方案数。
\(f_x\) 表示选边 \(x\) 的方案数,\(g_x\) 表示不选边 \(x\) 的方案数。

由于我们处理的所有点的度数都小于等于 2,因此设当前点为 \(top\),与其相连的两个点为 \(u,v\),对应的两条边分别为 \(x,y\)\(u,v\) 之间的边为 \(z\)。(如果有的话)

  1. 删 1 度点
    由于是生成树,每一个点都要包含,因此直接将 \(f_x\) 累乘到答案上即可。

  2. 缩 2 度点
    现在先假设 \(u,v\) 之间没有连边。那我们缩 2 度点就会变成在 \(u,v\) 之间新连一条边 \(z\)
    由于 \(top\) 必须被选,因此有

\[\begin{aligned} f_z &= f_xf_y \\ g_z&=f_xg_y+g_xf_y \end{aligned} \]

然后在 \(u,v\) 之间更新一下边的信息即可。

  1. 叠合重边
    因为输入时两个点间如果有多条边,我们可以将 \(f\) 直接设为重边的数量,这根据定义是显然的,因此一开始的图中本应没有重边。
    所以有重边的原因时操作二中在 \(u,v\) 之间连了一条新边 \(z'\),但是有可能 \(u,v\) 之间本身就有一条边 \(z\),因此我们要将这两条边合并起来。
    由于是生成树,两条边不能都选,但是可以都不选,因此方程式比较显然了:

\[\begin{aligned} f_z &= f_zg_{z'}+g_zf_{z'}\\ g_z&=g_zg_{z'} \end{aligned} \]

code

实现的时候用 map 来维护边的信息,复杂度阈值在 map\(O(n\log n)\)
可以看出来 2 操作与 3 操作是一体的,只有 2 操作过后 3 操作才有可能发生,因此要放在一起处理。
注意细节比较多,需要时刻注意边的细节维护完了没有。我因为在输入的时候没有初始化 \(g\) 被卡了半个小时。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=4e5+7,p=998244353;
int n,m,f[N],g[N],idcnt=0,ans=1,deg[N];
map <int,int> mp[N];
queue <int> q;
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;if(!mp[u][v]) mp[u][v]=mp[v][u]=++idcnt,deg[u]++,deg[v]++,g[idcnt]=1; //注意初始化 
		f[mp[u][v]]++;
	} 
	for(int i=1;i<=n;i++) if(deg[i]<=2) q.push(i);
	while(!q.empty()){
		int top=q.front();q.pop();
		if(deg[top]==0) continue;
		if(deg[top]==1){           //删1度点 
			int u,x;tie(u,x)=*mp[top].begin();
			ans=ans*f[x]%p;mp[u].erase(top);mp[top].erase(u),deg[u]--,deg[top]--;
			if(deg[u]<=2) q.push(u);
		}
		if(deg[top]==2){          //缩2度点 
			int u,v,x,y,z;tie(u,x)=*mp[top].begin();tie(v,y)=*next(mp[top].begin());z=mp[u][v];
			int F,G;F=(f[x]*f[y])%p,G=(f[x]*g[y]+f[y]*g[x])%p;
			if(!z){mp[u][v]=mp[v][u]=++idcnt;f[idcnt]=F,g[idcnt]=G;deg[u]++,deg[v]++;}
			else {f[z]=(f[z]*G+F*g[z])%p;g[z]=g[z]*G%p;}                            // 如果 u,v 间本身就有边,那就需要叠合重边 
			mp[u].erase(top),mp[v].erase(top),mp[top].erase(u),mp[top].erase(v);
			deg[u]--,deg[v]--,deg[top]-=2;
			if(deg[u]<=2) q.push(u);if(deg[v]<=2) q.push(v);
		}
	}
	cout<<ans<<'\n';return 0;
}
posted @ 2025-04-02 11:27  all_for_god  阅读(15)  评论(0)    收藏  举报