【NOIP2017模拟6.25】小W的动漫

题目

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

分析

这个题目有两个限制:
1、先父亲后儿子;2、先大儿子后小儿子。
这个处理起来就很麻烦,
于是,我们可以转化一下模型,
对于限制2,因为大儿子一定比小儿子先遍历,那么我们可以将小儿子当做前一个比它大的儿子的儿子。
如样例:这里写图片描述
现在,限制就只剩下“先父亲后儿子”,即求遍历一棵树,当父亲被走过才可以走儿子的方案数
显然,这是一棵二叉树。
f[x] 表示,遍历以x为根的子树的方案数。
转移:
设两个儿子分别为i,j(只有一个儿子的话,f[x]就等于儿子的f值),以i为根的子树大小为s1,j的为s2;

f[x]=f[i]f[j]Cmin(s1,s2)s1+s2
(C组合求的是插板问题)
其实就是当前有s1个点,按顺序插入s2个点中。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const int maxlongint=2147483647;
const long long mo=10007;
const int N=1005;
using namespace std;
long long son[N][2],size[N],f[N],jc[N*3],ny[N*3],n,m,T;
long long mi(long long x,long long y)
{
    long long sum=1;
    while(y) 
    {
        if(y&1) sum=sum*x%mo;
        x=x*x%mo;
        y>>=1;
    }
    return sum;
}
long long C(long long mm,long long nn)
{
    if(nn>mm) swap(nn,mm);
    return jc[mm]*ny[nn]%mo*ny[mm-nn];
}
void dg(int x)
{
    size[x]=1;
    int j=son[x][0],k=son[x][1];
    if(j) dg(j);
    if(k) dg(k);
    int s1=size[j],s2=size[k];
    size[x]+=s1+s2;
    if(j && k) f[x]=f[j]%mo*f[k]%mo*C(s1+s2,min(s1,s2))%mo;
    else
    if(j) f[x]=f[j];
    else f[x]=1;
}
int main()
{
    jc[0]=ny[0]=1;
    for(int i=1;i<=3000;i++)
    {
        jc[i]=jc[i-1]*i%mo;
        ny[i]=mi(jc[i],mo-2);
    }
    scanf("%lld",&T);
    for(;T--;)
    {
        memset(son,0,sizeof(son));
        memset(size,0,sizeof(size));
        memset(f,0,sizeof(f));
        f[0]=1;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
        {

            int last=i,x,k;
            scanf("%d",&k);
            for(int i=1;i<=k;i++)
            {
                scanf("%d",&x);
                if(!son[last][0]) son[last][0]=x;
                else son[last][1]=x;
                last=x;
            }
        }
        dg(1);
        printf("%lld\n",f[1]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值