Description
SPOJ PT07D
1:求n个点有标号无根树个数
2:求n个点有标号有根树个数
3:求n个点无标号有根树个数
4:求n个点无标号无根树个数
Solution
我太菜了现在才来做这道题
1和2很好求,略去
设fn为n个点无标号有根树,考虑删去根得到了一些无标号有根树
因为是无标号,考虑一种大小为k的子树,贡献为
1
+
x
k
+
x
2
k
+
x
3
k
+
.
.
.
=
1
1
−
x
k
1+x^k+x^{2k}+x^{3k}+...={1\over 1-x^k}
1+xk+x2k+x3k+...=1−xk1
且有fk种,所以
F
(
x
)
=
x
∏
k
>
=
1
1
(
1
−
x
k
)
f
k
F(x)=x\prod_{k>=1}{1\over (1-x^k)^{f_k}}
F(x)=x∏k>=1(1−xk)fk1
两边取ln
ln
F
(
x
)
=
l
n
x
−
∑
k
>
=
1
f
k
ln
(
1
−
x
k
)
\ln F(x)=ln x-\sum_{k>=1}f_k\ln (1-x^k)
lnF(x)=lnx−∑k>=1fkln(1−xk)
两边求导
F
′
(
x
)
F
(
x
)
=
1
x
+
∑
k
>
=
1
k
f
k
x
k
−
1
1
−
x
k
{F'(x)\over F(x)}={1\over x}+\sum_{k>=1}kf_k{x^{k-1}\over 1-x^k}
F(x)F′(x)=x1+∑k>=1kfk1−xkxk−1
x
F
′
(
x
)
=
F
(
x
)
+
F
(
x
)
∑
k
>
=
1
k
f
k
x
k
1
−
x
k
xF'(x)=F(x)+F(x)\sum_{k>=1}kfk{x^k\over 1-x^k}
xF′(x)=F(x)+F(x)∑k>=1kfk1−xkxk
两边取[x^n],
n
f
n
=
f
n
+
∑
i
=
1
n
−
1
f
i
[
x
n
−
i
]
∑
k
>
=
1
k
f
k
x
k
1
−
x
k
nfn=fn+\sum_{i=1}^{n-1}fi[x^{n-i}]\sum_{k>=1}kfk{x^k\over 1-x^k}
nfn=fn+∑i=1n−1fi[xn−i]∑k>=1kfk1−xkxk
注意到
x
k
1
−
x
k
=
x
k
+
x
2
k
+
x
3
k
+
.
.
.
.
{x^k\over 1-x^k}=x^k+x^{2k}+x^{3k}+....
1−xkxk=xk+x2k+x3k+....,即
(
n
−
1
)
f
n
=
∑
i
=
1
n
−
1
f
i
∑
k
∣
n
−
i
k
f
k
(n-1)fn=\sum_{i=1}^{n-1}fi\sum_{k|n-i}kfk
(n−1)fn=∑i=1n−1fi∑k∣n−ikfk
后面的东西可以提前记一下然后O(n^2)递推
当然也可以用分治NTT做到O(n log^2 n)
无标号无根树
首先先求出有根树fn,设无根树为hn,考虑如何唯一表示一棵树
容易想到重心,那么我们把根不是重心的全部减掉,不是重心就是有一个子树大小>n/2
h
n
=
f
n
−
∑
i
=
1
n
/
2
f
i
f
n
−
i
h_n=f_n-\sum_{i=1}^{n/2}f_if_{n-i}
hn=fn−∑i=1n/2fifn−i
注意当n为偶数时有两个重心,所以多减了两边子树相同的情况,要加回来
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int N=1e3+5;
int n,k,p,f[N],g[N];
int pwr(int x,int y) {
if (y<0) y+=p-1;
int z=1;x%=p;
for(;y;y>>=1,x=(ll)x*x%p)
if (y&1) z=(ll)z*x%p;
return z;
}
int solve_f(int n) {
f[1]=1;fo(j,1,n) g[j]=1;
fo(i,2,n) {
f[i]=0;
fo(j,1,i-1) f[i]=(f[i]+(ll)f[j]*g[i-j]%p)%p;
f[i]=(ll)f[i]*pwr(i-1,p-2)%p;
for(int j=i;j<=n;j+=i) g[j]=(g[j]+(ll)i*f[i]%p)%p;
}
return f[n];
}
int solve_h(int n) {
int ans=solve_f(n);
fo(i,1,n/2) ans=(ans-(ll)f[i]*f[n-i]%p+p)%p;
if (!(n&1)) {
ans=(ans+(ll)f[n/2]*f[n/2]%p)%p;
ans=(ans-(ll)f[n/2]*(f[n/2]-1)%p*pwr(2,p-2)%p+p)%p;
}
return ans;
}
int main() {
while (scanf("%d%d%d",&k,&n,&p)!=EOF) {
if (k==1) printf("%d\n",pwr(n,n-2));
if (k==2) printf("%d\n",pwr(n,n-1));
if (k==3) printf("%d\n",solve_f(n));
if (k==4) printf("%d\n",solve_h(n));
}
return 0;
}