Description
有(n)个人(n)个座位,需要给每个人确定一个(1-n)的编号,编号可以相同。
接着从第一个人开始依次入座,每个人会尝试坐到(a_i),如果(a_i)被占据了,就尝试(a_{i+1},a_{i+2}dots a_n)。如果尝试到第(n)个还不行,这个安排方案就不合法。同时有(m)个人的编号已经确定了,只能安排剩下的人的编号。求合法的安排方案。
Solution
首先考虑什么是不合法的方案。
设(sum[i])表示最多可以让多少人编号(leq i)
如果(sum[i]<i)那该方案就不合法
正确性挺显然的,就是入座的时候只可能编号小的人坐到编号大的座位上,不能从大到小坐。如果(sum[i]<i),那就是再怎么坐前(i)个也坐不满,肯定不合法。
然后考虑DP解决这个问题。考虑每个位置可以放哪些元素。
沿用刚才的状态设计思路,设(f[i][j])表示有(j)个人编号(leq i)的方案数,那么(ileq jleq sum[i])。
枚举编号恰好为(i)的有(k)个,那么(cnt[i]leq kleq j-(i-1)),其中(cnt[i])表示钦定编号为(i)的个数,(j-(i-1))是至少要给前(i-1)个位置留(i-1)个人来填满。
那转移就是(f[i][j]+=f[i-1][j-k] imes C(sum[i]-cnt[i]-j+k,k-cnt[i])),(sum[i]-cnt[i]-j+k)的意义是,给第(i)个最多留出来(sum[i]-cnt[i])个空位,再减去前(i-1)个用过的(j-k)个就是这么多了。
Code
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<cctype>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using std::min;
using std::max;
using std::swap;
using std::vector;
const int N=305;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define pii std::pair<int,int>
#define all(A) A.begin(),A.end()
#define mp(A,B) std::make_pair(A,B)
int sum[N],c[N][N];
int f[N][N],ZYZ,cnt[N];
int getint(){
int X=0,w=0;char ch=0;
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while( isdigit(ch))X=X*10+ch-48,ch=getchar();
if(w) return -X;return X;
}
signed main(){
int T=getint();
while(T--){
memset(f,0,sizeof f);
memset(c,0,sizeof c);
memset(cnt,0,sizeof cnt);
memset(sum,0,sizeof sum);
int n=getint(),m=getint();ZYZ=getint();
for(int i=1;i<=m;i++)
getint(),cnt[getint()]++;
sum[0]=n-m;int flag=0;
for(int i=1;i<=n;i++) {
sum[i]=sum[i-1]+cnt[i];
if(sum[i]<i) {
flag=1;
printf("NO
");
break;
}
} if(flag) continue;
c[0][0]=1;
for(int i=1;i<=n;i++){
c[i][0]=1;
for(int j=1;j<=n;j++)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%ZYZ;
}
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=i;j<=sum[i];j++){
for(int k=cnt[i];k<=j-i+1;k++)
f[i][j]=(1ll*f[i][j]+1ll*f[i-1][j-k]*c[sum[i]-cnt[i]-j+k][k-cnt[i]]%ZYZ)%ZYZ;
}
} printf("YES %d
",f[n][n]);
} return 0;
}