给n个人安排座位,先给每个人一个1~n的编号,设第i个人的编号为ai(不同人的编号可以相同),接着从第一个人开始,大家依次入座,第i个人来了以后尝试坐到ai,如果ai被占据了,就尝试ai+1,ai+1也被占据了的话就尝试ai+2,……,如果一直尝试到第n个都不行,该安排方案就不合法。然而有m个人的编号已经确定(他们或许贿赂了你的上司…),你只能安排剩下的人的编号,求有多少种合法的安排方案。由于答案可能很大,只需输出其除以M后的余数即可。
这道题很容易发现人的顺序是没有用的,只有编号出现的次数是有用的。只要满足sum[i]<=n-i+1和sum[1]=n(sum[i]表示编号>=i的个数)这两个条件就是合法的状态。
那就很容易发现这是dp,f[i][j]表示编号>=i的次数为j时的合法安排方案种数。dp方程再加一个组合数乱搞一下就可以了,那这道题就做完了。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
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*10+ch-'0',ch=getchar();
return x*f;
}
inline void write(int x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline void pr1(int x){write(x),putchar(' ');}
inline void pr2(int x){write(x),puts("");}
int cnt[310],sum[310],f[310][310],C[310][310];
int main()
{
//freopen("2302.in","r",stdin);
//freopen("2302.out","w",stdout);
int T=read();
while(T--)
{
memset(cnt,0,sizeof(cnt));memset(f,0,sizeof(f));
int n=read(),m=read(),P=read();
for(int i=1;i<=m;i++){int p=read(),q=read();cnt[q]++;}
C[0][0]=1;
for(int i=1;i<=300;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
}sum[n+1]=0;
for(int i=n;i>=1;i--)sum[i]=sum[i+1]+cnt[i];
bool bk=true;
for(int i=1;i<=n;i++)if(i+sum[i]-1>n){bk=false;break;}
if(bk==false){puts("NO");continue;}
f[n+1][0]=1;
for(int i=n;i>=1;i--)
{
for(int j=sum[i];i+j-1<=n;j++)
{
for(int k=sum[i]-cnt[i];k<=min(n-i,j-cnt[i]);k++)(f[i][j]+=1LL*f[i+1][k]*C[n-k-(sum[1]-sum[i+1])][j-k-cnt[i]]%P)%=P;
}
}
printf("YES %d\n",f[1][n]);
}
return 0;
}