SPOJ PT07D :Let us count 1 2 3 【树的计数】

传送门

解题思路:

四种树的计数方式:
1.有标号无根树:根据prufer序列可知是 nn2 n n − 2

2.有标号有根树:一棵有标号无根树以每个节点为根 ,所以是 nn1 n n − 1

3.无标号有根树:

fi f i 表示树的大小为 i i 的方案数,其生成函数是 F(x)=fixi
考虑到一棵无标号有根树可以看做一个无标号有根森林加一个根组成,而一种大小为 k k 的子树贡献用生成函数表示为xki=(1xk)1,一共有 fk f k 种,即为 (1xk)fk ( 1 − x k ) − f k ,所以:

F(x)=xk>0(1xk)fk F ( x ) = x ∏ k > 0 ( 1 − x k ) − f k

两边同取 In I n 再求导得:
F(x)F(x)=1x+k>0kfkxk11xk F ′ ( x ) F ( x ) = 1 x + ∑ k > 0 k f k x k − 1 1 − x k
xF(x)=F(x)+(k>0kfkxk1xk)F(x) x F ′ ( x ) = F ( x ) + ( ∑ k > 0 k f k x k 1 − x k ) F ( x )

再拆成每一项看,则:

nfn=fn+i>0fi([xni]k>0kfkxk1xk) n f n = f n + ∑ i > 0 f i ∗ ( [ x n − i ] ∑ k > 0 k f k x k 1 − x k )

看看 [xn]xk1xk [ x n ] x k 1 − x k ,即 i>0xki ∑ i > 0 x k i xn x n 项系数,为 [n|k] [ n | k ] ,所以:
(n1)fn=i>0fik|nikfk ( n − 1 ) f n = ∑ i > 0 f i ∑ k | n − i k f k

后面可以每次更新的时候存下来,所以直接递推复杂度是 O(n2) O ( n 2 ) 的。
分治fft之后可以做到 O(nlog2n) O ( n l o g 2 n )

4.无标号无根树。
hn h n 表示无根树的方案, fn f n 同上。考虑怎么唯一表示一棵树,我们可以用重心表示,所以把根不是重心的都减去。
一个根不为重心,那么有且仅有一个子树大小大于 n2 n 2 ,即:

hn=fni=1n/2fifni h n = f n − ∑ i = 1 n / 2 f i f n − i

注意当 n n 为偶数有两个重心,i=n/2时要特判,只减去两边子树不同的情况。
这个也可以卷积优化,特判时加回 f2n/2(fn/22) f n / 2 2 − ( f n / 2 2 ) 即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1005;
int k,n,mod,f[N],g[N],h[N];
int Pow(int x,int y)
{
    int res=1;
    for(;y;y>>=1,x=x*x%mod)
        if(y&1)res=res*x%mod;
    return res;
}
void init()
{
    f[1]=1;for(int i=1;i<=n;i++)g[i]=1;
    for(int i=2;i<=n;i++)
    {
        f[i]=0;
        for(int j=1;j<i;j++)f[i]=(f[i]+f[j]*g[i-j])%mod;
        f[i]=f[i]*Pow(i-1,mod-2)%mod;
        for(int j=i,t=i*f[i]%mod;j<=n;j+=i)g[j]=(g[j]+t)%mod;
    }
}
void init2()
{
    int inv2=(mod+1)/2;
    for(int i=1;i<=n;i++)
    {
        int cnt=0;
        for(int j=(i-1)/2;j;j--)cnt=(cnt+f[j]*f[i-j])%mod;
        if(!(i&1))cnt=(cnt+(ll)f[i/2]*(f[i/2]-1)*inv2%mod)%mod;
        h[i]=(f[i]-cnt+mod)%mod;
    }
}
int main()
{
    //freopen("lx.in","r",stdin);
    while(scanf("%d%d%d",&k,&n,&mod)!=EOF)
    {
        if(k==1)printf("%d\n",n==1?1:Pow(n%mod,n-2));
        else if(k==2)printf("%d\n",Pow(n%mod,n-1));
        else if(k==3)init(),printf("%d\n",f[n]);
        else init(),init2(),printf("%d\n",h[n]); 
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值