1. 程式人生 > >[HAOI2011] Problem c

[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;
}