写在前头:刚考完这题(被秒)就来发题解了
(first)
考试(DP)爆炸了,听(wh)大佬一波讲解之后醍醐灌顶,来(luogu)切了这题。先(orz)一波……
(second)
先明确一点,和人没有关系。
额,好像不太对。但意思差不多,人会被抽象成编号,我们关心的只是编号,和人没有关系。
再根据数据范围就可以确定状态应该是有两维。既然与人没有关系了,那么第一维自然变成了编号,至于第二维,(yy)一波也不难知道是编号小于等于(i)的人数(额,又扯到人上了,其实前面指的是与第几个人(即人的种类)无关,人数还是要的,不然不好转移)。
确定状态后就是确定转移方程了。
(third)
首先介绍两个要用的数组(sum,cnt),其中(cnt_i)指的是编号被强制定为(i)的人的数量,(sum_i)则是记录(sum_{j=1}^i cnt_j+n-m)的值,看到这里,应该有同学瞬间反应过来我要干什么了。别急,一步步看。
大家其实发现了,(sum_i)记录的不单单只是一个类似(cnt)前缀和的东西,更重要的作用是记录当前有多少人的编号能够小于等于(i).
同时这也是在(sum cnt)的基础上加上一个(n-m)的原因,加上的正是没有确定编号的人,让(sum)的作用能够转移方程且能判合法性。
易知:当前方案不合法时当且仅当(exists sum_i<i),因为若编号能够小于等于(i)的人数少于(i)个,前面的位置必将有空位,因为是(n)个人对应(n)个位置,故肯定有人没地方坐,故不合法。
(forth)
知道了(sum)的真正意义,那么转移方程也好推了。
先把方程给出来((f_{i,j})表示的是当前在编号(i)(之后可能有不合法的情况都不管,先搞当前的(i)),编号小于等于(i)的人的数量有(j)的方案数):
一步步地看这个式子。
首先(k)是枚举编号为(i)的人的数量,很明显,下限为(cnt_i).那么在之前的(i-1)个编号中肯定搞定了剩下的(j-k)个人,于是有了(f_{i-1,j-k}),但除了固定的(cnt_i)个人编号必须为(i)之外,还必须找(k-cnt_i)个自由人使得编号为(i)的总人数为(k),而这(k-cnt_i)个人得由我们在编号(i-1)及之前的人中(即(sum_{i-1}))且还未安排的人数中(即没被搞定,被搞定的人已经得出是(j-k))选出,即在(sum_{i-1}-(j-k)=sum_{i-1}-j+k)个人中选出(k-cnt_i)个人,组合数得到。
但还有一个费解的地方是(j)和(k)的循环范围,这个稍微想一下就可以知道了。在式子(f_{i,j})中,很明显成立的条件是(j>=i),只有当小于等于当前编号(i)的人数大于等于(i)才会使前面没有空缺,也才会满足题意,用这个不等式推一下就可以看出(j)的下限与(k)的上限了。
(fifth)
另外还有(j)的上限,很明显我们使编号小于等于(i)的人数最多只有(sum_i)个,上限即为(sum_i).
(i)直接从(1-n)跑一遍即可.
目标:(exists sum_i<ilor f_{n,n})
(sixth)
最后附上代码,(别忘了这题是个双倍经验:-)):
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;++i)
#define fdr(i,a,b) for(int i=a;i>=b;--i)
#define int long long
#define jinitaimei signed
inline int read()
{
int x=0;
char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x;
}
const int xx=3e2+10;
int f[xx][xx],sum[xx],cnt[xx],c[xx][xx];
inline void sol()
{
memset(f,0,sizeof(f));
memset(sum,0,sizeof(sum));
memset(cnt,0,sizeof(cnt));
int n=in,m=in,mod=in;
fur(i,0,n)
{
c[0][i]=1;
fur(j,1,i) c[j][i]=(c[j-1][i-1]+c[j][i-1])%mod;
}
fur(i,1,m)
{
int x=in;x=in;
++cnt[x];
}
sum[0]=n-m;
fur(i,1,n)
{
sum[i]=sum[i-1]+cnt[i];
if(sum[i]<i)
{
puts("NO");
return;
}
}
f[0][0]=1;
fur(i,1,n) fur(j,i,sum[i]) fur(k,cnt[i],j-i+1) (f[i][j]+=f[i-1][j-k]*c[k-cnt[i]][sum[i-1]-j+k])%=mod;
printf("YES %lld
",f[n][n]);
}
jinitaimei main()
{
int t=in;
while(t--) sol();
return 0;
}