[集训队作业]城市规划
题目
点这里看题目。
分析
考虑如下递推:
\(f_i\):\(i\)个点的无向有标号连通图的个数。
\(g_i\):\(i\)个点的无向有标号图的个数。
以下给出两种计算方式。
法一
不难看出一个式子:
\[g_n=\sum_{i=1}^n\binom{n-1}{i-1}f_ig_{n-i}
\]
这相当于枚举 1 所在的连通块大小,然后构造出这个连通块,剩下的点任意连。
然后感觉这个东西非常的 EGF ,就尝试拆一下:
\[\frac{g_n}{(n-1)!}=\sum_{i=1}^n\frac{f_i}{(i-1)!}\cdot\frac{g_{n-i}}{(n-i)!}
\]
由于负数的阶乘是未定义的,我们就认为它是 0 ,那么就可以简化下标:
\[\frac{g_n}{(n-1)!}=\sum_{i=0}^n\frac{f_i}{(i-1)!}\cdot\frac{g_{n-i}}{(n-i)!}
\]
另外,由于任意一个无向有标号图一定是:
\[\begin{aligned}
&G=(V,E')\\
&V=\{1,2,3,...,n\}\\
&E=\{(u,v)|u,v\in V\}, E'\subseteq E
\end{aligned}
\]
那么我们可以得到:
\[g_n=2^{\binom{n}{2}}
\]
实际上就是看每条边选不选。
带入到式子中可以得到:
\[\frac{2^{\binom n 2}}{(n-1)!}=\sum_{i=0}^n\frac{f_i}{(i-1)!}\cdot \frac{2^{\binom {n - i} 2}}{(n-i)!}
\]
反手写成生成函数:
\[\begin{aligned}
H(x)&=\sum_{i=1}^{+\infty} \frac{2^{\binom i 2}}{(i-1)!}x^i\\
F(x)&=\sum_{i=1}^{+\infty} \frac{f_i}{(i-1)!}x^i\\
G(x)&=\sum_{i=0}^{+\infty} \frac{2^{\binom i 2}}{i!}x^i
\end{aligned}
\]
于是有:
\[\begin{aligned}
H(x)&=F(x)*G(x)\\
F(x)&=H(x)*G^{-1}(x)
\end{aligned}
\]
多项式求逆,结束。时间是\(O(n\log_2n)\)。
法二
为了方便,我们先不考虑标号,记:
\[p_i=\frac{f_i}{i!},q_i=\frac{g_i}{i!}
\]
把它写成生成函数的形式:
\[\begin{aligned}
P(x)&=\sum_{i=0}^{+\infty} p_ix^i\\
Q(x)&=\sum_{i=0}^{+\infty} q_ix^i
\end{aligned}
\]
然后可以发现:
\[\begin{aligned}
Q(x)=\sum_{k=0}^{+\infty} \frac{(P(x))^k}{k!}
\end{aligned}
\]
其中\(k\)是在枚举连通块的个数;除以\(k!\),是因为\((P(x))^k\)使得连通块之间存在顺序,然而这样就算重复了,因此要除去。
发现等式右边是经典的\(e^x\)的展开式,缩回去得到:
\[Q(x)=e^{P(x)}
\]
转为\(P(x)=\ln Q(x)\)计算即可。时间是\(O(n\log_2n)\)。
题外话
两个方法都是正确的,但是结果看起来还不太一样,我们进一步分析一下:
可以发现,多项式存在如下的关系:
\[H(x)=xG'(x),F(x)=xP'(x)
\]
那么就有:
\[\begin{aligned}
F(x)&=\frac{H(x)}{G(x)}\\
x\cdot P'(x)&=x\cdot \frac{G'(x)}{G(x)}\\
P'(x)&=\ln' G(x)\\
P(x)&=\ln G(x)
\end{aligned}
\]
所以说两个方法的结果是统一的。只不过法一的代码简单一些而已
代码
作者太懒了,只有法一的代码
#include <cmath>
#include <cstdio>
typedef long long LL;
const int mod = 1004535809, phi = mod - 1, g = 3;
const int MAXN = 1e6 + 5;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
template<typename _T>
void swapp( _T &x, _T &y )
{
_T t = x; x = y, y = t;
}
int rev[MAXN], wp[MAXN], wpinv[MAXN];
int Q[MAXN], G[MAXN], H[MAXN];
int fac[MAXN], finv[MAXN];
int N;
int qkpow( int base, int indx )
{
int ret = 1;
while( indx )
{
if( indx & 1 ) ret = 1ll * ret * base % mod;
base = 1ll * base * base % mod, indx >>= 1;
}
return ret;
}
int inv( const int a ) { return qkpow( a, phi - 1 ); }
int fix( const int a ) { return ( a % mod + mod ) % mod; }
void init( const int len )
{
int lg2 = log2( len );
for( int i = 0 ; i < len ; i ++ )
rev[i] = wp[i] = wpinv[i] = 0;
for( int i = 0 ; i < len ; i ++ )
for( int j = 0 ; j < lg2 ; j ++ )
rev[i] |= ( i >> j & 1 ) << ( lg2 - j - 1 );
wp[0] = 1, wp[1] = qkpow( g, phi / len );
wpinv[0] = 1, wpinv[1] = qkpow( g, phi - phi / len );
for( int i = 2 ; i < len ; i ++ )
wp[i] = 1ll * wp[i - 1] * wp[1] % mod,
wpinv[i] = 1ll * wpinv[i - 1] * wpinv[1] % mod;
}
void NTT( int *coe, const int len, const int t )
{
#define p ( s >> 1 )
for( int i = 0 ; i < len ; i ++ )
if( rev[i] < i )
swapp( coe[i], coe[rev[i]] );
int w, wo, we;
for( int s = 2 ; s <= len ; s <<= 1 )
for( int i = 0 ; i < len ; i += s )
for( int j = 0 ; j < s >> 1 ; j ++ )
{
w = t > 0 ? wp[len / s * j] : wpinv[len / s * j];
we = coe[i + j], wo = 1ll * coe[i + j + p] * w % mod;
coe[i + j] = ( we + wo ) % mod, coe[i + j + p] = ( we - wo + mod ) % mod;
}
#undef p
if( t > 0 ) return; int inver = inv( len );
for( int i = 0 ; i < len ; i ++ ) coe[i] = 1ll * coe[i] * inver % mod;
}
namespace PolyInv
{
int P[MAXN], F0[MAXN], F[MAXN], B[MAXN];
void PolyInv( const int n )
{
if( n == 1 ) { F[0] = inv( P[0] ); return; }
int p = n + 1 >> 1, len = 1;
PolyInv( p );
while( len < n << 1 ) len <<= 1; init( len );
for( int i = 0 ; i < n ; i ++ ) B[i] = P[i];
for( int i = 0 ; i < len ; i ++ ) F0[i] = F[i], F[i] = 0;
NTT( B, len, 1 ), NTT( F0, len, 1 );
for( int i = 0 ; i < len ; i ++ ) F[i] = 1ll * F0[i] * fix( 2 - 1ll * B[i] * F0[i] % mod ) % mod;
NTT( F, len, -1 );
for( int i = n ; i < len ; i ++ ) F[i] = 0;
}
void PolyInv( int *ret, int *A, const int n )
{
for( int i = 0 ; i < n << 2 ; i ++ ) P[i] = F0[i] = F[i] = B[i] = 0;
for( int i = 0 ; i < n ; i ++ ) P[i] = A[i];
PolyInv( n );
for( int i = 0 ; i < n ; i ++ ) ret[i] = F[i];
}
}
namespace PolyMul
{
int A[MAXN], B[MAXN];
void PolyMul( int *ret, int *a, const int La, int *b, const int Lb )
{
int len = 1;
while( len < La + Lb ) len <<= 1;
init( len );
for( int i = 0 ; i < len ; i ++ ) ret[i] = A[i] = B[i] = 0;
for( int i = 0 ; i < La ; i ++ ) A[i] = a[i];
for( int i = 0 ; i < Lb ; i ++ ) B[i] = b[i];
NTT( A, len, 1 ), NTT( B, len, 1 );
for( int i = 0 ; i < len ; i ++ ) ret[i] = 1ll * A[i] * B[i] % mod;
NTT( ret, len, -1 );
for( int i = La + Lb ; i < len ; i ++ ) ret[i] = 0;
}
}
int main()
{
read( N );
if( N == 0 ) return puts( "0" ), 0;
fac[0] = 1, finv[1] = 1;
for( int i = 2 ; i <= N ; i ++ ) finv[i] = 1ll * ( mod - mod / i ) * finv[mod % i] % mod;
finv[0] = 1;
for( int i = 1 ; i <= N ; i ++ ) fac[i] = 1ll * fac[i - 1] * i % mod, finv[i] = 1ll * finv[i - 1] * finv[i] % mod;
N ++, G[0] = 1;
for( int i = 1 ; i < N ; i ++ )
{
int tmp = qkpow( 2, 1ll * i * ( i - 1 ) / 2 % phi );
G[i] = 1ll * tmp * finv[i] % mod, H[i] = 1ll * tmp * finv[i - 1] % mod;
}
PolyInv :: PolyInv( G, G, N );
PolyMul :: PolyMul( Q, G, N, H, N );
write( 1ll * Q[N - 1] * fac[N - 2] % mod ), putchar( '\n' );
return 0;
}