大致题意: 求出(n)个点的简单(无重边无自环)有标号无向连通图数目。
前言
在我的想象里,这道题应该是无比冗长、十分复杂、令人懵逼的题面+极其恐怖、难以理解、又臭又长的式子+分治(NTT)+多项式求逆+多项式对数+指数型生成函数+一堆五花八门、乱七八糟的东西。
然而真正点开这道题,题面简洁明了(就一句话),题解里式子推出来我居然也能看得懂,代码里也没啥乱七八糟的东西,就一个多项式求逆+(NTT)。
或许是我看的题解比较良心?
推式子
这是一个常见的套路。(我也不知道常见不常见,反正我似乎曾遇到过,还被闪指导指导过)
我们设(f(x))为有(x)个点的无向连通图数目(即答案),(g(x))为有(x)个点的图的数目。
对于(g(x)),我们只要考虑共计(frac{x(x-1)}2)条边中每条边是否连在图上,即:
[g(x)=2^{frac{x(x-1)}2}
]
然后我们就要想办法把这个式子变复杂让(f(x))和(g(x))扯上关系。
如果我们对于(1)号点,去枚举它所在的连通块大小,那么就有:
[g(x)=sum_{i=1}^xC_{x-1}^{i-1}f(i)g(x-i)
]
即,从剩余的共(n-1)个节点选出(i-1)个点与(1)号点拼成连通块,而其他的点可以随意。
接下来这一步我可以确信是一个常见套路,就是拆了组合数(此处我们顺便代入(g(x))的值),得到:
[2^{frac{x(x-1)}2}=sum_{i=1}^xfrac{(x-1)!}{(i-1)!(x-i)!}f(i)2^{frac{(x-i)(x-i-1)}2}
]
移项,得到:
[frac{2^{frac{x(x-1)}2}}{(x-1)!}=sum_{i=1}^xfrac{f(i)}{(i-1)!}cdotfrac{2^{frac{(x-i)(x-i-1)}2}}{(x-i)!}
]
然后我们就发现每一项都有着自己对应的下标,即设(其中(f(x)[i])表示函数(f(x))的(i)次项系数):
[F(x)[i]=frac{f(i)}{(i-1)!}(ige 1)
]
[G(x)[i]=frac{2^{frac{i(i-1)}2}}{i!}(ige 0)
]
[H(x)[i]=frac{2^{frac{i(i-1)}2}}{(i-1)!}(ige 1)
]
则我们发现原式相当于:(此处用(n)替换上式的(x),以免定义重复)
[H(x)[n]=sum_{i=1}^nF(x)[i]cdot G(x)[n-i]
]
显然这就是一个卷积的形式,得到:
[H=F*G
]
那么就有:
[F=H*G^{-1}
]
因此直接先预处理出(G,H),然后多项式求逆+(NTT)求出(F),则(f(n)=F(x)[n] imes(n-1)!),这样一来就搞定了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 130000
#define X 1004535809
#define QI(x) QP(x,X-2)
using namespace std;
int n,g[N<<2],g_[N<<2],h[N<<2],Fac[N+5],IFac[N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
class Poly//多项式模板
{
private:
#define PR 3
#define IPR 334845270
int P,L,R[N<<2],t[N<<2];
I void T(int *s,CI p)
{
RI i,j,k,U,S,x,y;for(i=0;i^P;++i) i<R[i]&&(s[i]^=s[R[i]]^=s[i]^=s[R[i]]);
for(i=1;i^P;i<<=1) for(U=QP(p,(X-1)/(i<<1)),j=0;j^P;j+=i<<1) for(S=1,k=0;k^i;
++k,S=1LL*S*U%X) s[j+k]=((x=s[j+k])+(y=1LL*S*s[i+j+k]%X))%X,s[i+j+k]=(x-y+X)%X;
}
public:
I void Inv(CI n,int *a,int *b)//多项式求逆
{
if(!n) return (void)(b[0]=QI(a[0]));Inv(n>>1,a,b);RI i;
P=1,L=0;W(P<=(n<<1)) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
for(i=0;i<=n;++i) t[i]=a[i];T(b,PR),T(t,PR);
for(i=0;i^P;++i) b[i]=(2LL*b[i]-1LL*t[i]*b[i]%X*b[i]%X+X)%X;T(b,IPR);
RI t=QI(P);for(i=0;i<=n;++i) b[i]=1LL*b[i]*t%X;for(;i^P;++i) b[i]=0;
}
I int NTT(CI n,int *a,int *b)//NTT,只用求出第n项系数
{
RI i;P=1,L=0;W(P<=(n<<1)) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
for(T(a,PR),T(b,PR),i=0;i^P;++i) a[i]=1LL*a[i]*b[i]%X;return T(a,IPR),1LL*a[n]*QI(P)%X;
}
}P;
int main()
{
RI i;scanf("%d",&n);for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;
for(IFac[n]=QI(Fac[n]),i=n-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;
for(i=0;i<=n;++i) g[i]=1LL*QP(2,(1LL*i*(i-1)/2)%(X-1))*IFac[i]%X;//预处理g
for(i=1;i<=n;++i) h[i]=1LL*QP(2,(1LL*i*(i-1)/2)%(X-1))*IFac[i-1]%X;//预处理h
return P.Inv(n,g,g_),printf("%d",1LL*P.NTT(n,g_,h)*Fac[n-1]%X),0;//多项式求逆后NTT
}