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;
}