[hackerrank]Unique Divide And Conquer

题目大意

有多少种不同的n个节点的带编号无根树点分治过程唯一?

DP

设f[i]表示i个节点时的答案。
g[i]表示i个节点组成的森林,森林中每一个棵树都是点分治过程唯一的,而且是有根树,有多少种。
对于f的计算,首先重心有i种编号,然后思考除去重心后的森林部分,可以用g[i-1]表示除了该层外点分治过程唯一的方案,然后减去非法的情况。
非法情况包括:删去点不是重心,点分治过程不唯一。
两种情况可以合成一种:森林中存在一颗树的子树大小为j,且j>=(i+1)/2。(可以自己探究为什么是(i+1)/2,这里的除法当然是整除)
那么我们这个j,首先这棵树的点分治过程必须唯一,而且还要是有根树(对应g的方案),那么根的方案可以有j种选择(注意这里为什么不会算重,不一样的选根可能使得树的形态一致,但编号一定不一致。如果编号也一致,则原来一定算重了!)。同时我们要从i-1种编号中选j个组成这棵树,于是得到f的递推式。
f[i]=i(g[i1]i+12j=1f[j]jg[ij1]Cji1)
再来考虑g如何递推。
为了不算重,我们思考如下过程:
每次删掉当前编号最小点所在的树。
那么一颗森林删除方法唯一。
因此枚举编号最小点所在的树大小j,这棵树点分治过程唯一且有根,同样是f[j]*j,其余是g[i-j],然后编号最小点一定在,因此我们要从i-1个编号选出j-1个编号给这颗树。
g[i]=ij=1f[j]jg[ij]Cj1i1
递推f和g,f[n]就是答案。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=3000+10;
int f[maxn],g[maxn],fac[maxn],inv[maxn];
int i,j,k,l,t,n,mo;
int quicksortmi(int x,int y){
    if (!y) return 1;
    int t=quicksortmi(x,y/2);
    t=(ll)t*t%mo;
    if (y%2) t=(ll)t*x%mo;
    return t;
}
int C(int n,int m){
    if (n<m) return 0;
    return (ll)fac[n]*inv[m]%mo*inv[n-m]%mo;
}
int main(){
    scanf("%d%d",&n,&mo);
    fac[0]=1;
    fo(i,1,n) fac[i]=(ll)fac[i-1]*i%mo;
    inv[n]=quicksortmi(fac[n],mo-2);
    fd(i,n-1,0) inv[i]=(ll)inv[i+1]*(i+1)%mo;
    g[0]=1;
    fo(i,1,n){
        t=g[i-1];
        fo(j,(i+1)/2,i-1)
            t=(t-(ll)f[j]*g[i-j-1]%mo*C(i-1,j)%mo*j%mo)%mo;
        f[i]=(ll)i*t%mo;
        fo(j,1,i)
            g[i]=(g[i]+(ll)g[i-j]*f[j]%mo*C(i-1,j-1)%mo*j%mo)%mo;
    }
    (f[n]+=mo)%=mo;
    printf("%d\n",f[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值