写在题解之前:感谢 \(\texttt{rfy}\) 大佬的讲解。
【题目描述】
给 \(n\) 个人安排座位,先给每个人一个 \(1\)~\(n\) 的编号,设第 \(i\) 个人的编号为 \(a_i\)(不同人的编号可以相同),
接着从第一个人开始,大家依次入座,第 \(i\) 个人来了以后尝试坐到 \(a_i\),如果 \(a_i\) 被占据了,就尝试 \(a_i+1\),\(a_i+1\) 也被占据了的话就尝试 \(a_i+2,……,\) 如果一直尝试到第 \(n\) 个都不行,该安排方案就不合法。
然而有 \(m\) 个人的编号已经确定(他们或许贿赂了你的上司...),你只能安排剩下的人的编号,求有多少种合法的安排方案。
由于答案可能很大,只需输出其除以 \(M\) 后的余数即可。
【输入格式】
第一行一个整数 \(T\),表示数据组数。
对于每组数据,第一行有三个整数,分别表示 \(n\)、\(m\)、\(M\)。
若 \(m\) 不为 \(0\),则接下来一行有 \(m\) 对整数,\(p_1\)、\(q_1\),\(p_2\)、\(q_2\) ,…, \(p_m\)、\(q_m\),其中第 \(i\) 对整数 \(p_i\)、\(q_i\) 表示第 \(p_i\) 个人的编号必须为 \(q_i\)。
【输出格式】
对于每组数据输出一行,若是有解则输出 \(\text{YES}\),后跟一个整数表示方案数 \(\mod M\) ,注意,\(\text{YES}\) 和数之间只有一个空格,否则输出 \(\text{NO}\)。
【样例输入】
2
4 3 10
1 2 2 1 3 1
10 3 8882
7 9 2 9 5 10
【样例输出】
YES 4
NO
【数据范围与提示】
\(100\%\) 的数据满足:\(1\leq T\leq 10\),\(1\leq n\leq 300\),\(0\leq m\leq n\),\(2\leq M\leq 10^9\),\(1\leq pi,qi\leq n\) 且保证 \(p_i\) 互不相同。
我们考虑一个合法的方案满足的条件,即对于任意一个编号 \(i\) 的数,都需要保证编号小于等于 \(i\) 的人数大于等于 \(i\)。
反正法:反正它是对的。
反证法:假设编号小于等于 \(i\) 的人数小于 \(i\)。
那么,编号大于 \(i\) 的人数就大于 \(n-i\) 后面的人数就超出限制,所以不合法。
所以我们用 \(s_i\) 来表示编号小于等于 \(i\) 的人数,如果不合法则直接输出 NO。
同时,用 \(num_i\) 来记录编号为 \(i\) 位上有几个人。
考虑用 dp 来进行状态转移。
设 \(f(i,j)\) 表示有 \(j\) 个人的编号小于等于 \(i\) 的情况。
我们设编号确定为 \(i\) 的有 \(k\) 人,所以编号小于 \(i\) 的也就有 \(j-k\) 个人,而且第 \(i\) 位要从非限制的人中选 \(k-num_i\) 位,就可以得到:
\(f(i,j)=f(i,j)+f(i-1,j-k) \times C(s_i-j+k-num_i,k-num_i)\)
最后的答案就是 \(f(n,n)\)。
\(p.s.\) YES 和 NO 一定要大写QAQ。
代码如下:
#include<bits/stdc++.h>
#define rint register int
using namespace std;
inline int read(){
int s=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=0;c=getchar();}
while(c>='0'&&c<='9')s=(s<<1)+(s<<3)+c-48,c=getchar();
return f?s:-s;
}
int n,m,Mod,f[310][310],C[310][310];
int num[310],s[310];
void pre_work(){
C[0][0]=C[1][0]=C[1][1]=1;
for(rint i=2;i<=n;i++){
C[i][0]=1;
for(rint j=1;j<=n;j++)
C[i][j]=1ll*(C[i-1][j]+C[i-1][j-1])%Mod;
}
}
int main(){
int T=read();
while(T--){
memset(num,0,sizeof num);
memset(s,0,sizeof s);
memset(f,0,sizeof f);
n=read(); m=read(); Mod=read();
pre_work();
for(rint i=1;i<=m;++i) read(),num[read()]++;
s[0]=n-m;
bool flag(1);
for(rint i=1;i<=n;++i){
s[i]=s[i-1]+num[i];
if(s[i]<i){
flag=0; break;
}
}
if(!flag){
printf("NO\n");
continue;
}
f[0][0]=1;
for(rint i=1;i<=n;++i)
for(rint j=max(i,num[i]);j<=s[i];++j)
for(rint k=num[i];k<=j-i+1;++k)
f[i][j]=1ll*(f[i][j]+1ll*f[i-1][j-k]*C[s[i]-num[i]-j+k][k-num[i]]%Mod)%Mod;
printf("YES %d\n",f[n][n]);
}
return 0;
}