LOJ#6389. 「THUPC2018」好图计数 / Count【生成函数】

传送门

解题思路:

终于卡过去了……
这题和求无标号有根树个数的思路差不多,可以先看这里,因为下面一些公式演算会省略中间过程。

设大小为 n n 的好图数目为fn,其中连通的数目为 gn g n
注意但 n2 n ≥ 2 时,不连通的好图和连通的好图一一对应,即 gn=fn/2 g n = f n / 2

考虑生成函数 F(x)=fixi F ( x ) = ∑ f i x i ,为何方便,我们设 f0=1 f 0 = 1
一个好图是由若干个连通好图拼成,即

F(x)=k>0(i0xik)gk=k>0(1xk)gk F ( x ) = ∏ k > 0 ( ∑ i ≥ 0 x i k ) g k = ∏ k > 0 ( 1 − x k ) − g k

两边取 In I n 再求导得
F(x)F(x)=k>0kgkxk11xk F ′ ( x ) F ( x ) = ∑ k > 0 k g k x k − 1 1 − x k

F(x)=F(x)k>0kgkxk11xk F ′ ( x ) = F ( x ) ∑ k > 0 k g k x k − 1 1 − x k

再拆回每一项看:
[xn]F(x)=(n+1)fn+1=i=0nfi([xni]k>0kgkxk11xk) [ x n ] F ( x ) = ( n + 1 ) f n + 1 = ∑ i = 0 n f i ( [ x n − i ] ∑ k > 0 k g k x k − 1 1 − x k )

注意 xk11xk=xk1+x2k1+x3k1+... x k − 1 1 − x k = x k − 1 + x 2 k − 1 + x 3 k − 1 + . . . ,所以 [xn]xk11xk=[k|n+1] [ x n ] x k − 1 1 − x k = [ k | n + 1 ] ,所以

(n+1)fn+1=i=0nfik|n+1ikgk ( n + 1 ) f n + 1 = ∑ i = 0 n f i ∑ k | n + 1 − i k g k

注意 i=0 i = 0 时要移项,且 f0=1,gi=fi/2 f 0 = 1 , g i = f i / 2 ,所以得:
n+12fn+1=k|n+1,k<n+1kgk+i=1nfik|n+1ikgk n + 1 2 f n + 1 = ∑ k | n + 1 , k < n + 1 k g k + ∑ i = 1 n f i ∑ k | n + 1 − i k g k

si=k|ikgk s i = ∑ k | i k g k 就可以 O(n2) O ( n 2 ) 递推了,卡常可过。
也可以分治FFT做到 O(nlog2n) O ( n l o g 2 n )

#include<bits/stdc++.h>
#define ll unsigned long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=100005;
int T,n=23333,mod;
inline void add(ll &x,int y){x=x+y>=mod?x+y-mod:x+y;}
ll f[N],g[N],s[N];
ll Pow(ll x,int y)
{
    ll res=1;
    for(;y;y>>=1,x=x*x%mod)
        if(y&1)res=res*x%mod;
    return res;
}
int main()
{
    //freopen("lx.in","r",stdin);
    T=getint(),mod=getint();
    f[0]=f[1]=1;for(int i=1;i<=n;i++)s[i]=1;
    for(int i=1;i<n;i++)
    {
        __int128 tmp=0;
        for(int j=0;j<=i;j++)tmp+=f[j]*s[i+1-j];tmp%=mod;
        g[i+1]=tmp*Pow(i+1,mod-2)%mod,f[i+1]=g[i+1]*2%mod;
        for(int j=i+1;j<=n;j+=i+1)add(s[j],tmp);
    }
    while(T--)cout<<f[getint()]<<'\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值