BZOJ 3456 城市规划 生成函数 NTT

题意

n n 个有标号点的无向联通图个数

Solution

发现直接求解很复杂,将其用别的函数表示出来

f(x)为包含 1 1 号节点的节点数为x 无向连通图 个数, g(x) g ( x ) 为节点数为 x x 无向图 个数

这两个函数有以下关系:

g(n)=i=1nCn1i1f(i)g(ni)

组合数在卷积内不好维护,直接拆开

g(n)=i=1n(n1)!(i1)!(ni)!f(i)g(ni) g ( n ) = ∑ i = 1 n ( n − 1 ) ! ( i − 1 ) ! ( n − i ) ! f ( i ) g ( n − i )

g(n)(n1)!=i=0n1f(i+1)i!g(ni1)(ni1)! g ( n ) ( n − 1 ) ! = ∑ i = 0 n − 1 f ( i + 1 ) i ! g ( n − i − 1 ) ( n − i − 1 ) !

显然 g(n)=2C2n g ( n ) = 2 C n 2

考虑构建生成函数

A(n)=i=1g(i)(i1)!xi A ( n ) = ∑ i = 1 ∞ g ( i ) ( i − 1 ) ! x i

B(n)=i=0g(i)i!xi B ( n ) = ∑ i = 0 ∞ g ( i ) i ! x i

C(n)=i=0f(i)(i1)! C ( n ) = ∑ i = 0 ∞ f ( i ) ( i − 1 ) !

可得 A(x)=B(x)C(x) A ( x ) = B ( x ) C ( x )

modxn+1 mod x n + 1 条件下, A(x)B1(x)=C(x) A ( x ) B − 1 ( x ) = C ( x )

所以直接求逆做多项式乘法即可

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int mod = 1004535809 , G = 3 , N = 270000;
int n , m = 1 , rev[N];
int frc[N];
int A[N] , B[N] , C[N];
int INVB[N];
int read() {
    int ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
int qpow(int a , int b) {
    int ans = 1;
    while(b) {
        if(b & 1) ans = 1ll * ans * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return ans;
}
void dft(int *now , int n , int f) {
    for(int i = 0 ; i < n ; ++ i)
        if(i < rev[i]) swap(now[i] , now[rev[i]]);
    for(int i = 1 ; i < n ; i <<= 1) {
        int gn = qpow(G , (mod - 1) / (i << 1));
        if(f != 1) gn = qpow(gn , mod - 2);
        for(int j = 0 ; j < n ; j += (i << 1)) {
            int x , y , g = 1;
            for(int k = 0 ; k < i ; ++ k , g = 1ll * g * gn % mod) {
                x = now[j + k] , y = 1ll * now[i + j + k] * g % mod;
                now[j + k] = (x + y) % mod;
                now[i + j + k] = (x - y + mod) % mod;
            }
        }
    }
    if(f != 1) {
        int ny = qpow(n , mod - 2);
        for(int i = 0 ; i < n ; ++ i) now[i] = 1ll * now[i] * ny % mod;
    }
}
void inv(int *a , int *b , int deg) {
    if(deg == 1) {b[0] = qpow(a[0] , mod - 2); return;}
    inv(a , b , (deg + 1) >> 1);
    int l = 0 , nn  , n = deg * 2;
    for(nn = 1 ; nn < n ; nn <<= 1) ++ l;
    for(int i = 0 ; i < nn ; ++ i)
        rev[i] = (rev[i>>1]>>1) | ((i & 1) << (l - 1));
    for(int i = 0 ; i < deg ; ++ i) C[i] = a[i];
    for(int i = deg ; i < nn ; ++ i) C[i] = 0;
    dft(C , nn , 1); dft(b , nn , 1);
    for(int i = 0 ; i < nn ; ++ i) b[i] = 1ll * (2 - 1ll * C[i] * b[i] % mod + mod ) % mod * b[i] % mod;
    dft(b , nn , -1);
    for(int i = deg ; i < nn ; ++ i) b[i] = 0;
}
int main() {
    n = read();
    frc[0] = 1;

    for(int i = 1 ; i <= n ; ++ i) frc[i] = 1ll * frc[i - 1] * i % mod;
    for(int i = 1 ; i <= n ; ++ i)
        A[i] = 1ll * qpow(2 , 1ll * i * (i - 1) / 2 % (mod - 1)) * qpow(frc[i - 1] , mod - 2) % mod;
    for(int i = 0 ; i <= n ; ++ i)
        B[i] = 1ll * qpow(2 , 1ll * i * (i - 1) / 2 % (mod - 1)) * qpow(frc[i] , mod - 2) % mod;

    inv(B , INVB , n);

    while(m <= n) m <<= 1;
    int l = 0 , nn;
    for(nn = 1 ; nn <= m ; nn <<= 1) ++ l;
    for(int i = 0 ; i < nn ; ++ i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
    dft(INVB , nn , 1); dft(A , nn , 1);
    for(int i = 0 ; i < nn ; ++ i) A[i] = 1ll * A[i] * INVB[i] % mod;
    dft(A , nn , -1);
    printf("%lld\n" , 1ll * A[n] * frc[n - 1] % mod);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值