[HAOI2011] Problem c

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\)的方案数,那么\(i\leq j\leq sum[i]\)

枚举编号恰好为\(i\)的有\(k\)个,那么\(cnt[i]\leq k\leq j-(i-1)\),其中\(cnt[i]\)表示钦定编号为\(i\)的个数,\(j-(i-1)\)是至少要给前\(i-1\)个位置留\(i-1\)个人来填满。

那转移就是\(f[i][j]+=f[i-1][j-k]\times 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\n");
                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\n",f[n][n]);
    } return 0;
}
posted @ 2018-10-31 09:09  YoungNeal  阅读(115)  评论(0编辑  收藏  举报