JZOJ5418. 【NOIP2017提高A组集训10.24】合影

87 篇文章 0 订阅
18 篇文章 0 订阅

Description

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

Input

  第一行包含一个整数T,表示数据组数
  对于每一组数据,第一行两个整数n, m, p,分别表示志愿者数、奇奇怪怪的要求的个数和模数,保证p为质数。
  接下来m行,每行两个整数x,y ,表示x必须在y左边。

Output

输出总共T行,第i行的数为第i组询问对应的答案ansi

Sample Input

2
3 1 17
1 2
5 0 101

Sample Output

3
19

样例说明
对于第一组询问,总共三种方案:
123 132 312
对于第二组询问:由于没有限制,所以总共有5!=120种不同的方案,模101后是19

Data Constraint

对于15%的数据,n,m<=9
对于30%的数据, n,m<=17
对于50%的数据, n,m<=20
对于70%的数据, n,m<=2000
对于100%的数据, n<=2*10^5,m<=2*10^5,m<=n,n+10<=p<=10^9+7,T<=10

题解

需要注意到题目中有一句话“每个人最多只会提出一个”,
这说明了是一片森林,可能存在有环。
有环的情况就输出0。

fi 表示以i为根节点的子树的方案数,
si 表示以i为根节点的子树的大小,

转移比较简单,方案数相乘,再乘上一个组合数。

code

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#define ll long long
#define N 200003
#define db double
#define P putchar
#define G getchar
#define mo 998244353
using namespace std;
char ch;
void read(int &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    ll w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
ll abs(ll x){return x<0?-x:x;}
ll sqr(ll x){return x*x;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}
void writeln(ll x){write(x);P('\n');}

int n,x,y,p,m,T,s[N];
int to[N],nxt[N],b[N],tot;
ll jc[N],ny[N],f[N];
bool bz[N];

void ins(int x,int y)
{
    nxt[++tot]=b[x];
    to[tot]=y;
    b[x]=tot;
}

ll ksm(ll x,int y)
{
    ll s=1;
    while(y)
    {
        if(y&1)s=s*x%p;
        x=x*x%p;
        y>>=1;
    }
    return s;
}

ll C(int x,int y)
{
    return jc[y]*ny[x]%p*ny[y-x]%p;
}

void dfs(int x)
{
    f[x]=1;
    for(int i=b[x];i;i=nxt[i])
    {
        dfs(to[i]);
        s[x]+=s[to[i]];
        f[x]=f[x]*f[to[i]]%p*C(s[to[i]],s[x])%p;
    }
    s[x]++;
}

int main()
{
    freopen("photo.in","r",stdin);
    freopen("photo.out","w",stdout);
    read(T);
    while(T--)
    {
        read(n);read(m);read(p);ny[0]=jc[0]=1;
        for(int i=1;i<=n;i++)
            jc[i]=jc[i-1]*i%p,ny[i]=ksm(jc[i],p-2);
        tot=0;
        memset(f,0,sizeof(f));
        memset(s,0,sizeof(s));
        memset(b,0,sizeof(b));
        memset(bz,1,sizeof(bz));
        for(int i=1;i<=m;i++)
            read(x),read(y),ins(y,x),bz[x]=0;

        for(int i=1;i<=n;i++)
            if(bz[i])ins(0,i);

        dfs(0);
        if(s[0]!=n+1)writeln(0);else writeln(f[0]);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值