【JZOJ 5418】【NOIP2017提高A组集训10.24】合影

Description

经过一天的忙碌,志愿者们结束了他们的工作,准备站在一排合影留念。
现在总共有n名志愿者留下来准备合影。不过,进程并不是那么顺利,有些同学提出了一些奇奇怪怪的要求(每个人最多只会提出一个):他必须站在另外一个同学的左边(不一定相邻),仁慈的老师满足了他们的要求。这时,其中一位来自11班的同学小Z陷入了沉思:总共有多少种不同的合法方案数呢?(两种方案不同当且仅存在至少一名同学他在这两个方案当中站的位置不同。)小Z很快就算出来了,于是就把自己的这个问题告诉了好朋友小C。不过,由于小C的数学功底不足,小Z只要求他算出这个答案模质数p的余数就可以了。可就算这样,小C也不会做。为了显示自己的水平很高(实际上很低),他找到了你,并把你得出的答案报给小Z,所以你可一定要算对啊!

Solution

注意到题目的那句话了吗,
限制条件是一棵树,不是一个DAG!!!
好了,现在这题变成大水题了,直接树形DP,用C公式即可

复杂度: O(n)

Code

#include <cstdio>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define efo(i,q) for(int i=A[q];i;i=B[i][0])
#define min(q,w) ((q)>(w)?(w):(q))
#define max(q,w) ((q)<(w)?(w):(q))
using namespace std;
typedef long long LL;
const int N=200500;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,ans,mo;
LL jc[N],jcn[N];
int B[2*N][2],A[N],B0;
int b[N],z[N];
bool BK;
struct qqww
{
    int si;
    LL ans;
}a[N];
LL ksm(LL q,int w)
{
    LL ans=1;
    for(q%=mo;w;w>>=1,q=q*q%mo)if(w&1)ans=ans*q%mo;
    return ans;
}
void link(int q,int w)
{
    b[w]++;
    B[++B0][0]=A[q];A[q]=B0,B[B0][1]=w;
}
LL C(int m,int n){return jc[m]*jcn[n]%mo*jcn[m-n]%mo;}
int dfs(int q)
{
    if(z[q]){BK=1;return 0;}
    a[q].ans=1;a[q].si=0;
    z[q]=1;
    efo(i,q)
    {
        a[q].si+=dfs(B[i][1]);
        if(BK)return 0;
        a[q].ans=a[q].ans*C(a[q].si,a[B[i][1]].si)%mo*a[B[i][1]].ans%mo;
    }
    return ++a[q].si;
}
int main()
{
    int q,w,_;
    read(_);jc[0]=jcn[0]=1;
    while(_--)
    {
        BK=0;
        read(n),read(m),read(mo);
        fo(i,1,n+1)A[i]=b[i]=z[i]=0,jc[i]=jc[i-1]*(LL)i%mo;
        jcn[n+1]=ksm(jc[n+1],mo-2);
        fod(i,n,1)jcn[i]=jcn[i+1]*(LL)(i+1)%mo;
        B0=A[0]=z[0]=0;
        fo(i,1,m)read(w),read(q),link(q,w);
        fo(i,1,n)if(!b[i])link(0,i);
        dfs(0);
        q=0;
        fo(i,1,n)if(z[i])q++;
        if(q!=n)BK=1;
        if(BK)printf("0\n");
        else printf("%lld\n",a[0].ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值