可以注意到题目中所说合法序列的充要条件是对于所有i,<=i的数至少出现过i次
那么我们可以设dp[i][j]代表小于i的数已经出现了j次
那么转移方程就很显然了
dp[i][j]=segma(dp[i-1][k]*C[j-k-ap[i]][n-m-(k-tl[i-1])]) (k+ap[i]<=j)
ap[i]为确定为i的个数,tl[i]为确定小于i的个数
代码:
#include<bits/stdc++.h> #define LL long long #define INF 1000000000 using namespace std; inline double _fabs(double a) {return a>0?a:-a;} inline int read() { int ret=0,f=1;char c=getchar(); while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();} while(c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();} return ret*f; } inline int gcd(int a,int b) { int t; while(b) {t=a;a=b;b=t%b;} return a; } #define fix 1000 #define MAXN 305 #define MAXM 10005 int n,m,ap[MAXN],tl[MAXN],T,M;LL dp[MAXN][MAXN]; void init() { memset(dp,0,sizeof(dp)); memset(ap,0,sizeof(ap)); } int C[MAXN][MAXN]; void pre() { for(int i=0;i<MAXN;i++) C[0][i]=C[i][0]=1; for(int i=1;i<MAXN;i++) for(int j=1;j<MAXN;j++) C[i][j]=(C[i-1][j]+C[i][j-1])%M; } bool check() { int now=0; for(int i=n;i>0;i--) { now+=ap[i]; if(now>n-i+1) return 1; } return 0; } int main() { T=read(); while(T--) { init(); n=read();m=read();M=read(); for(int i=1;i<=m;i++) read(),ap[read()]++; if(check()) {puts("NO");continue;} m=n-m;pre();dp[0][0]=1; for(int i=1;i<=n;i++) tl[i]=tl[i-1]+ap[i]; for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) for(int k=tl[i];k<=j;k++){ if(j<tl[i]) break; dp[i][j]+=dp[i-1][k-ap[i]]*C[j-k][m-j+tl[i]]%M; dp[i][j]%=M; } printf("YES %d ",dp[n][n]); } return 0; }
代码不知道哪里写丑了还是什么,慢了一个档。。。