LOJ#3323. 「SNOI2020」生成树

题目描述

给定无向连通图 G G G,已知 G G G 在删掉一条边后是一颗仙人掌(仙人掌:不存在两个拥有公共边的简单环的无向联通图),求 G G G 的生成树个数。结果对 998244353 998244353 998244353 取模。
1 ≤ n , m ≤ 5 ∗ 1 0 5 1\le n,m\le5*10^5 1n,m5105

题目分析

如果就是原图是仙人掌那么答案显然是所有环大小之积。

G G G 删掉一条边后是仙人掌,那么 G G G 一定是这样的形式:

LOJ#3323. 「SNOI2020」生成树_第1张图片
就是一个大环串起了一些仙人掌串,没有被大环串起的对答案的贡献就是乘上环大小。
而且其中必然存在至少一条不在小环中的边(只有两个环贴在一起的情况特殊考虑,实现相同)
就是上图中的红色边,记这样的边的条数为 c n t cnt cnt,记小环的大小为 s i z i siz_i sizi,小环两半边大小分别为 l i , r i l_i,r_i li,ri

那么形成生成树要么是断一条红边,所有环断一条边,要么是断两条不在同一半的环边,其它环断一条边。答案为 c n t ∗ ( ∏ s i z i ) + ( ∑ l i ∗ r i s i z i ) ∗ ( ∏ s i z i ) cnt*(\prod siz_i)+(\sum {l_i*r_i\over siz_i})*(\prod siz_i) cnt(sizi)+(siziliri)(sizi)

那么问题就是要求出 c n t cnt cnt s i z i siz_i sizi 以及每个环某一半的大小。

先tarjan一遍求出这个大环对应的点双,将其拿出来建新图(除去不相关的点和边)
这个大环一定满足 边数 > 点数,tarjan求点双的边数可以通过在栈中加入边解决。

然后断开一条红色边,使其形成一个仙人掌,观察可以发现度数为3的点一定有一条是红色边,所以枚举某个度数为3的点的所有边断开检验是否是仙人掌即可。

在得到仙人掌的过程中就可以记录得到的点双大小,某一半的大小可以通过记录dfs深度相减得到,因为环上只有进入和出去的点的度数>2,可以借此求解。

想清楚了还是蛮好做的。

Code:

#include
#define maxn 500005
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const int mod = 998244353;
int n,m,prd=1,inv[maxn],dis[maxn],X[maxn],Y[maxn],deg[maxn];
int dep[maxn],dfn[maxn],low[maxn],stk[maxn*2],top,tim,sz;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot=1;
vector<int>P,Q[maxn]; int id;
void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
bool ban[maxn],onc[maxn];
void tarjan(int u,int ff){
	dfn[u]=low[u]=++tim,stk[++top]=u;
	for(int i=fir[u],v;i;i=nxt[i]) if(!ban[i/2]&&i^1^ff){
		if(!dfn[v=to[i]]){
			stk[++top]=-i,dep[v]=dep[u]+1,tarjan(v,i),low[u]=min(low[u],low[v]);
			if(dfn[u]==low[v]){
				Q[++sz]=vector<int>(1,u); int edg=0;
				for(int t=0;t!=-i;){
					if((t=stk[top--])<0) edg++;
					else {Q[sz].push_back(t); if(deg[t]>2) dis[sz]=dep[t]-dep[u];}
				}
				if(edg>Q[sz].size()) id=sz;
			}
			else if(dfn[u]<low[v]) top-=2;
		}
		else if(dfn[u]>dfn[v]) stk[++top]=-i,low[u]=min(low[u],dfn[v]);
	}
}
int main()
{
	read(n),read(m);
	inv[0]=inv[1]=1; for(int i=2;i<=n;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=m;i++) read(X[i]),read(Y[i]),line(X[i],Y[i]),line(Y[i],X[i]);
	tarjan(1,0);
	for(int i=1;i<=sz;i++) if(i!=id) prd=1ll*prd*Q[i].size()%mod;
	if(!id) return printf("%d\n",prd),0;
	P=Q[id];
	memset(fir,0,(n+1)<<2),tot=1;
	for(int i=P.size()-1;i>=0;i--) onc[P[i]]=1;
	int M=0;
	for(int i=1;i<=m;i++) if(onc[X[i]]&&onc[Y[i]]) M++,deg[X[i]]++,deg[Y[i]]++,line(X[i],Y[i]),line(Y[i],X[i]);
	for(int k=P.size()-1,u;k>=0;k--) if(deg[u=P[k]]==3)
		for(int j=fir[u];j;j=nxt[j]){
			ban[j/2]=1;
			memset(dfn,0,(n+1)<<2),dep[u]=0,top=tim=sz=id=0;
			tarjan(u,0);
			if(id) {ban[j/2]=0; continue;}
			int cnt=0,pw=1,sum=0;
			for(int i=1,siz;i<=sz;i++){
				siz=Q[i].size(),pw=1ll*pw*siz%mod,cnt+=siz;
				sum=(sum+1ll*dis[i]*(siz-dis[i])%mod*inv[siz])%mod;
			}
			return printf("%d\n",1ll*(sum+M-cnt)*pw%mod*prd%mod),0;
		}
}

你可能感兴趣的:(LOJ#3323. 「SNOI2020」生成树)