JZOJ5165. 【NOIP2017模拟6.25】小W的动漫

Description

小W最近迷上了日本动漫,每天都有无数部动漫的更新等着他去看,所以他必须将所有的动漫排个顺序,当然,虽然有无数部动漫,但除了1号动漫,每部动漫都有且仅有一部动漫是它的前传(父亲),也就是说,所有的动漫形成一个树形结构。而动漫的顺序必须满足以下两个限制:
1、一部动漫的所有后继(子孙)都必须排在它的后面;
2、对于同一部动漫的续集(孩子),小W喜爱度高的须排在前面。
光排序小W还不爽,他想知道一共有多少种排序方案,并且输出它mod 10007的答案。

题解

题目给了两种排序的限制,我们可以通过转换,使其变为一种。
在同一个节点的儿子中,喜爱度高的应该排在前面,那我们就将喜爱度高的那一个节点作为喜爱度仅次于它的父亲。
这样之后,限制就变为了一种:父亲 要在儿子前面。
而且转换之后,树变成了一棵二叉树,
接下来就是如何求方案数。
对于每一个节点,我们记录 fi,si 表示这个节点为根的子树的方案数和树的大小。
那么合并的时候就应该将两个儿子节点的方案数乘起来,再乘上排序的方案数。
对于两个大小为 a,b 的子树,它们的排序的方案数就是: Caa+b
最后的答案就是 f1 了。

code(c++)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 1003
#define mo 10007
using namespace std;
int f[N],s[N],jc[2*N],ny[2*N];
int T,n,m,l[N],r[N],fa,x;
int ksm(int x,int y)
{
    int s=1;
    while(y)
    {
        if(y%2)s=(s*x)%mo;
        x=(x*x)%mo;
        y/=2;
    }
    return s;
}
void ins(int x,int y)
{
    if(l[x])r[x]=y;else l[x]=y;
}
int C(int n,int m)
{
    return((ny[m-n]*jc[m])%mo*ny[n])%mo;
}
void tree(int x)
{
    if(l[x])tree(l[x]);
    if(r[x])tree(r[x]);
    s[x]=s[l[x]]+s[r[x]]+1;
    if(r[x])
    {
        int s1=s[l[x]],s2=s[r[x]];
        f[x]=((f[l[x]]*f[r[x]])%mo*C(s1,s1+s2))%mo;
    }else if(l[x])f[x]=f[l[x]];else f[x]=1;
}
int main()
{
    ny[1]=jc[1]=1;
    for(int i=2;i<2*N;i++)
        jc[i]=(jc[i-1]*i)%mo,ny[i]=ksm(jc[i],mo-2);
    scanf("%d",&T);
    while(T--)
    {
        memset(l,0,sizeof(l));
        memset(r,0,sizeof(r));
        memset(f,0,sizeof(f));
        memset(s,0,sizeof(s));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&m);
            fa=i;
            for(int j=1;j<=m;j++)
                scanf("%d",&x),ins(fa,x),fa=x;
        }
        tree(1);
        printf("%d\n",f[1]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值