【BZOJ3456】城市规划-多项式求逆

测试地址:城市规划
题目大意: n n 个点带标号简单无向连通图(即无重边,无自环)的数目。
做法:本题需要用到多项式求逆。
如果不要求连通,这题就是水题了,答案显然为2Cn2(即枚举每条边选或不选)。
然而这题显然没那么简单,我们令 f(n) f ( n ) 为我们要求的答案, g(n)=2C2n g ( n ) = 2 C n 2 ,有如下递推式:
g(n)=n1i=1Ci1n1f(i)g(ni) g ( n ) = ∑ i = 1 n − 1 C n − 1 i − 1 f ( i ) g ( n − i )
上面式子的意义实质上是枚举点 1 1 所在的连通块的大小i进行转移。
当然,我们要求的不是 g g 而是f,所以我们需要找到一个方式用 g g 表示出f
把上面的组合数拆开,得到:
g(n)=(n1)!n1i=1f(i)(i1)!g(ni)(ni)! g ( n ) = ( n − 1 ) ! ∑ i = 1 n − 1 f ( i ) ( i − 1 ) ! ⋅ g ( n − i ) ( n − i ) !
两边同除一个 (n1)! ( n − 1 ) ! ,这就是一个卷积形式的式子,于是我们可以用三个生成函数(多项式)来表示这三个部分:
A(x)=B(x)C(x) A ( x ) = B ( x ) C ( x )
C C 为包含f(i)的那个部分,于是 C(x)=A(x)B1(x) C ( x ) = A ( x ) B − 1 ( x ) ,其中 C1(x) C − 1 ( x ) 是多项式 C C 的逆。
于是接下来就涉及多项式求逆的问题了,在这里,令C的最高次项的次数为 n n ,那么它的逆C1(x)应该满足:
C(x)C1(x)1(modxn+1) C ( x ) C − 1 ( x ) ≡ 1 ( mod x n + 1 )
于是问题转化成求一个模意义下的多项式 D(x) D ( x ) ,使得:
C(x)D(x)1(modxn) C ( x ) D ( x ) ≡ 1 ( mod x n )
假设我们已经求出一个 D(x) D ′ ( x ) 满足:
C(x)D(x)1(modxn2) C ( x ) D ′ ( x ) ≡ 1 ( mod x ⌈ n 2 ⌉ )
于是我们有:
D(x)D(x)0(modxn2) D ′ ( x ) − D ( x ) ≡ 0 ( mod x ⌈ n 2 ⌉ )
两边平方,把模数扩展到 n n (也有可能是n+1,反正不小于 n n ,不管):
D2(x)2D(x)D(x)D2(x)(modxn)
两边再乘一个 C(x) C ( x ) ,有:
D(x)2D(x)C(x)D2(x)(modxn) D ( x ) ≡ 2 D ′ ( x ) − C ( x ) D ′ 2 ( x ) ( mod x n )
而我们知道, C(x) C ( x ) 在模 x x 意义下的逆元,就等于它常数项的逆元。于是这样我们就能倍增+NTT算出多项式的逆了,算法的时间复杂度为:
T(n)=T(n2)+O(nlogn)=O(nlogn)
这样我们就解决了这一题。注意因为上面的所有计算都是在模意义下进行的,所以每一步都要将多项式取模(其实就是截取前面的某一段)。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1004535809;
const ll g=3;
ll n,rev[600010]={0},fac[600010],inv[600010];
ll A[600010]={0},B[600010]={0},C[600010]={0};
ll p[600010]={0},s[600010]={0};

ll power(ll a,ll b)
{
    ll s=1,ss=a;
    b=(b%(mod-1)+mod-1)%(mod-1);
    while(b)
    {
        if (b&1) s=s*ss%mod;
        ss=ss*ss%mod;b>>=1;
    }
    return s;
}

ll NTT(ll *a,ll type,ll n)
{
    for(ll i=0;i<n;i++)
        if (i<rev[i]) swap(a[i],a[rev[i]]);
    for(ll mid=1;mid<n;mid<<=1)
    {
        ll W=power(g,type*(mod-1)/(mid<<1));
        for(ll l=0;l<n;l+=(mid<<1))
        {
            ll w=1;
            for(ll k=0;k<mid;k++,w=w*W%mod)
            {
                ll x=a[l+k],y=w*a[l+mid+k]%mod;
                a[l+k]=(x+y)%mod;
                a[l+mid+k]=(x-y+mod)%mod;
            }
        }
    }
    if (type==-1)
    {
        ll inv=power(n,mod-2);
        for(ll i=0;i<=n;i++)
            a[i]=a[i]*inv%mod;
    }
}

ll calc_rev(ll limit)
{
    ll bit=0,x=1;
    while(x<=limit) bit++,x<<=1;
    rev[0]=0;
    for(ll i=1;i<=x;i++)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    return x;
}

void calc_inv(ll *a,ll len)
{
    if (len==1)
    {
        s[0]=power(a[0],mod-2);
        return;
    }
    calc_inv(a,(len+1)>>1);

    ll x=calc_rev((len<<1)-1);
    memset(p,0,sizeof(p));
    for(ll i=0;i<len;i++)
        p[i]=a[i];
    NTT(s,1,x),NTT(p,1,x);
    for(ll i=0;i<=x;i++)
        s[i]=(2ll*s[i]-p[i]*s[i]%mod*s[i]%mod+mod)%mod;
    NTT(s,-1,x);
    for(ll i=len;i<=x;i++)
        s[i]=0;
}

int main()
{
    scanf("%lld",&n);
    fac[0]=fac[1]=1;
    for(ll i=1;i<=n;i++)
        fac[i]=fac[i-1]*i%mod;
    inv[n]=power(fac[n],mod-2);
    for(ll i=n;i>=1;i--)
        inv[i-1]=inv[i]*i%mod;

    for(ll i=0;i<=n;i++)
    {
        if (i>0) A[i]=power(2ll,i*(i-1)/2ll)*inv[i-1]%mod;
        B[i]=power(2ll,i*(i-1)/2ll)*inv[i]%mod;
    }
    calc_inv(B,n+1);

    ll x=calc_rev(n);
    NTT(s,1,x),NTT(A,1,x);
    for(ll i=0;i<=x;i++)
        C[i]=s[i]*A[i]%mod;
    NTT(C,-1,x);
    printf("%lld",C[n]*fac[n-1]%mod);

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值