题解 P5827【点双连通图计数】
:修改一处式子错误。
题意
求 个点的有标号点双连通图数量,对 取模。
。
思路
前置
设有根无向连通图的 为 ,无根点双连通图的 为 。
我们显然可以使用有标号无向连通图的 乘上 以得到 ,于是考虑用 互相表示。
对于一个有根无向连通图,每个点都会被包含在至少 个点双中,且每个点双的大小不少于 。注意还需要特判 时输出 。
考虑断掉根连出的边,无向连通图被划分成若干连通块,根据 的集合意义,求出单个连通块的 即可。
对于根所在的任意一个点双连通块,若在其任何除根以外的一点连上一个以该点为根的无向连通图不会影响根所在的任意一个点双的大小,故单个连通块的 为
可以直接得到
令 ,有 ,根据扩展拉格朗日反演有
至此直接求解即可。
时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff
}
const int mod=998244353,N=524288+10;
namespace Poly{
int n,m,a[N],b[N],c[N],s[N],ss[N],d[N],e[N];
int f[N],g[N],h[N],F[N],tmp1[N],tmp2[N],tmp3[N];
int rev[N],inv[N],fac[N],ifac[N],G[19][N],lim;
inline void init(int n,int mode=1){
if(mode){
int l=0;
for(lim=1;lim<n;lim<<=1)l++;
for(int i=1;i<lim;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
}else{
for(lim=1;lim<n;lim<<=1);
}
}
inline int qpow(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
inline void Prefix(int n){
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=1ll*fac[i-1]*i%mod;
ifac[n]=qpow(fac[n],mod-2);
for(int i=n;i>=1;i--)
ifac[i-1]=1ll*ifac[i]*i%mod;
for(int i=1,p=1;i<=18;i++,p<<=1){
G[i][0]=1;
G[i][1]=qpow(3,mod-1>>i);
for(int j=2;j<p;j++)
G[i][j]=1ll*G[i][j-1]*G[i][1]%mod;
}
}
inline void NTT(int *a,int t){
for(int i=0;i<lim;i++)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(int i=1,t=1;i<lim;i<<=1,t++){
for(int j=0;j<lim;j+=i<<1){
int t1,t2;
for(int k=0;k<i;k++){
t1=a[j+k];
t2=1ll*G[t][k]*a[i+j+k]%mod;
a[j+k]=(t1+t2)%mod;
a[i+j+k]=(t1-t2+mod)%mod;
}
}
}
if(t==1) return ;
int Inv=qpow(lim,mod-2);
reverse(a+1,a+lim);
for(int i=0;i<lim;i++)
a[i]=1ll*a[i]*Inv%mod;
}
inline void Inv(int *a,int *b,int n){
if(n==1){
b[0]=qpow(a[0],mod-2);
return ;
}
Inv(a,b,n+1>>1);
init(n<<1);
for(int i=0;i<n;i++)
c[i]=a[i];
for(int i=n;i<lim;i++)
c[i]=0;
NTT(c,1),NTT(b,1);
for(int i=0;i<lim;i++)
b[i]=1ll*(2-1ll*c[i]*b[i]%mod+mod)%mod*b[i]%mod;
NTT(b,-1);
for(int i=n;i<lim;i++)
b[i]=0;
}
inline void Mul(int *a,int *b,int n){
init(n<<1);
memset(c,0,lim<<2);
memcpy(c,b,n<<2);
NTT(a,1),NTT(c,1);
for(int i=0;i<lim;i++)
a[i]=1ll*a[i]*c[i]%mod;
NTT(a,-1);
}
inline void Der(int *a,int *b,int n){
for(int i=1;i<n;i++)
b[i-1]=1ll*i*a[i]%mod;
b[n-1]=0;
}
inline void Int(int *a,int *b,int n){
for(int i=1;i<n;i++)
b[i]=1ll*a[i-1]*inv[i]%mod;
b[0]=0;
}
inline void polyln(int *a,int *b,int n){
static int p[N];
memset(p,0,n<<3);
memset(d,0,n<<3);
Der(a,p,n);
Inv(a,d,n);
Mul(p,d,n);
Int(p,b,n);
}
inline void Log(int n,int *f,int *g){
init(n);
polyln(f,g,lim);
for(int i=n;i<lim;i++)
g[i]=0;
}
inline void polyexp(int n,int *a,int *b){
if(n==1){
b[0]=1;
return ;
}
polyexp(n+1>>1,a,b);
Log(n,b,s);
for(int i=0;i<n;i++)
s[i]=a[i]>=s[i]?a[i]-s[i]:a[i]+mod-s[i];
for(int i=n;i<lim;i++)
b[i]=s[i]=0;
s[0]++;
NTT(s,1),NTT(b,1);
for(int i=0;i<lim;i++)
b[i]=1ll*b[i]*s[i]%mod;
NTT(b,-1);
for(int i=n;i<lim;i++)
b[i]=0;
}
inline void Exp(int n,int *a,int *b){
polyexp(n,a,b);
}
}
using namespace Poly;
int ask[5],L;
inline int solve(int m){
memset(g,0,sizeof(g));
memset(tmp1,0,sizeof(tmp1));
m--;
if(!m) return 1;
for(int i=0;i<L;i++)
g[i]=1ll*(mod-m)*tmp3[i]%mod;
Exp(L,g,tmp1);
init(L<<1);
NTT(tmp1,1);
for(int i=0;i<lim;i++)
tmp1[i]=1ll*tmp1[i]*tmp2[i]%mod;
NTT(tmp1,-1);
return 1ll*tmp1[m-1]*fac[m-1]%mod;
}
int main(){
for(int i=0;i<5;i++)
ask[i]=read(),n=max(n,ask[i]+1);
init(n,0);
L=lim;
Prefix(L);
for(int i=0;i<L;i++)
f[i]=1ll*qpow(2,1ll*i*(i-1)/2%(mod-1))*ifac[i]%mod;
Log(L,f,F);
Der(F,F,L);
Log(L,F,tmp3);
Der(tmp3,tmp2,L);
NTT(tmp2,1);
for(int i=0;i<5;i++)
write(solve(ask[i])),putc('\n');
flush();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)