传送门

先考虑如何判断无解,设 $sum[i]$ 表示确定的人中,编号大于 $i$ 的人的人数

如果 $sum[i]>n-i+1$ 则无解,进一步考虑设 $f[i][j]$ 表示当前确定完编号大于等于 $i$ 的人,除去原本固定的人还有 $j$ 人已经确定

那么有 $f[i][j]=\sum_{k=0}^{j}f[i+1][j-k] \cdot C_{j}^{k},j \in [0,n-i+1-sum[i]]$

表示在确定 $j-k$ 人的编号的情况下,再选 $k$ 个人编号为 $i$,乘上组合数是因为每个人都是不同的,我们可以在 $j$ 个人中任意选择 $k$ 个编号为 $i$

记得组合数每次都要重新算,因为模数不同...

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=507;
int T,n,m,mo,sum[N];
ll C[N][N],f[N][N];
inline ll fk(ll x) { return x>=mo ? x-mo : x; }
int main()
{
    T=read();
    while(T--)
    {
        memset(f,0,sizeof(f)); int a,b,flag=1;
        memset(sum,0,sizeof(sum));
        n=read(),m=read(),mo=read();
        for(int i=1;i<=m;i++)
        {
            a=read(),b=read();
            sum[b]++;
        }
        for(int i=n;i;i--)
        {
            sum[i]+=sum[i+1];
            if(sum[i]>n-i+1) { flag=0; break; }
        }
        if(!flag) { printf("NO\n"); continue; }
        // f[i][j]+=f[i+1][j-k]*C[j][k]
        for(int i=0;i<=300;i++)
        {
            C[i][0]=1;
            for(int j=1;j<=i;j++)
                C[i][j]=fk(C[i-1][j]+C[i-1][j-1]);
        }
        f[n+1][0]=1;
        for(int i=n;i>=1;i--)
            for(int j=0;j<=n-i+1-sum[i];j++)
                for(int k=0;k<=j;k++)
                    f[i][j]=fk(f[i][j]+f[i+1][j-k]*C[j][k]%mo);
        printf("YES %lld\n",f[1][n-m]);
    }
    return 0;
}