题解 【[HAOI2011]Problem c】

写在题解之前:感谢 \(\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
#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]

你可能感兴趣的:(题解 【[HAOI2011]Problem c】)