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

传送门

解题思路:

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

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

3.无标号有根树:

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

F ( x ) = x k > 0 ( 1 x k ) f k

两边同取 I n 再求导得:
F ( x ) F ( x ) = 1 x + k > 0 k f k x k 1 1 x k
x F ( x ) = F ( x ) + ( k > 0 k f k x k 1 x k ) F ( x )

再拆成每一项看,则:

n f n = f n + i > 0 f i ( [ x n i ] k > 0 k f k x k 1 x k )

看看 [ x n ] x k 1 x k ,即 i > 0 x k i x n 项系数,为 [ n | k ] ,所以:
( n 1 ) f n = i > 0 f i k | n i k f k

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

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

h n = f n i = 1 n / 2 f i f n i

注意当 n 为偶数有两个重心, i = n / 2 时要特判,只减去两边子树不同的情况。
这个也可以卷积优化,特判时加回 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;
}

猜你喜欢

转载自blog.csdn.net/cdsszjj/article/details/80435120