LOJ3323. 「SNOI2020」生成树

给出一个图,满足删掉其中一条边之后可以变成一棵仙人掌。问生成树个数。

\(n,m\le 10^5\)


传统做法:可以感受到图应该是一个大环串着多个环,然后在上面接一些子仙人掌。首先找到一条边,满足删掉这条边之后变成一棵仙人掌。如果删这条边,答案为仙人掌环长乘积;如果不删这条边,对其它部分建圆方树,然后求出这条边对应两点之间路径,枚举哪个环割掉两条边,其它环割掉一条边。可以\(O(n)\)做。(找边:先跑tarjan求出所有强联通分量,在强联通分量内求点度。边一定在存在三度点的强联通分量内。找到这个三度点,枚举三条边,分别判定)

模板做法:广义串并联图。

定义:不存在四个点,满足存在四个点两两之间路径的集合,使其不能在端点之外的地方相交,的无向图。

只要广义串并联图,可以通过收缩操作,即:删1度点、缩2度点、叠合重边,最终变成一个点。(不是广义串并联图的这样操作下去显然不能变成一个点)

可以DP。对于当前图中,\(f_x,g_x\)分别表示当前边对应的边形单元是联通还是不连通的方案数。

删1度点:答案乘\(f_x\)

缩2度点:\(f=f_xf_y,g=g_xf_y+f_xg_y\)

叠合重边:\(f=g_xf_y+f_xg_y,g=g_xg_y\)

进行收缩的时候维护一下即可。

收缩的时候,最好能叠合重边就先叠合重边,然后再删1度点或缩2度点。否则容易出奇怪的问题。


using namespace std;
#include <bits/stdc++.h>
#define N 500005
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define mo 998244353
int n,m;
struct edge{int u,v;int to(int z){return u^v^z;}} ed[N];
multiset<int> nei[N];
map<pair<int,int>,int> e;
priority_queue<pair<int,int> > q;
int cnt;
ll f[N],g[N],ans;
void era(int x){
	int u=ed[x].u,v=ed[x].v;
	nei[u].erase(x);
	nei[v].erase(x);
	if (e[mp(u,v)]==x)
		e.erase(mp(u,v));
}
void ins(int x){
	int u=ed[x].u,v=ed[x].v;
	nei[u].insert(x);
	nei[v].insert(x);
	if (e.find(mp(u,v))==e.end())
		e[mp(u,v)]=x;
	else
		q.push(mp(1,x));
}
void check(int z){
	if (nei[z].size()==2)
		q.push(mp(0,z));
}
void work(){
	for (int i=1;i<=n;++i)
		if (nei[i].size()<=2)
			q.push(mp(0,i));
	while (!q.empty()){
		int x=q.top().fi,y=q.top().se;
		q.pop();
		if (x){
			x=e[mp(ed[y].u,ed[y].v)];
			if (x==y) continue;
			f[x]=(f[x]*g[y]+g[x]*f[y])%mo;
			g[x]=g[x]*g[y]%mo;
			era(y);
			check(ed[x].u);
			check(ed[x].v);
		}
		else{
			int z=y;
			if (nei[z].size()==1){
				x=*nei[z].begin();
				ans=ans*f[x]%mo;
				era(x);
				check(ed[x].to(z));
			}
			else if (nei[z].size()==2){
				x=*nei[z].begin(),y=*nei[z].rbegin();
				int u=ed[x].to(z),v=ed[y].to(z);
				g[x]=(f[x]*g[y]+g[x]*f[y])%mo;
				f[x]=f[x]*f[y]%mo;
				era(x);
				era(y);
				if (u>v) swap(u,v);
				ed[x]={u,v};
				ins(x);
			}
		}
	}
}
int main(){
//	freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=0;i<m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		if (u>v) swap(u,v);
		ed[i]={u,v};
		ins(i);
	}
	for (int i=0;i<m;++i) 
		f[i]=g[i]=1;
	ans=1;
	work();
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-05-27 18:51  jz_597  阅读(202)  评论(0编辑  收藏  举报