#前缀和优化dp#牛客练习赛71 C 数学考试

题目

\(1\sim n\)的排列,有\(m\)个限制条件,第\(i\)个限制条件\(p_i\),
表示前\(p_i\)个数不能是\(1\sim p_i\)的排列,求符合要求的排列的个数。


分析

这里是单纯计数的做法,时间复杂度\(O(n^2)\)
\(dp[i][j]\)表示前\(i\)个数均\(\leq j\)并且必须包含\(j\)的方案数,
初始化\(dp[1][1\sim n]=1\)(如果第一个数有限制要特判),最后输出\(dp[n][n]\)
首先可以写出一个朴素的方程,

\[dp[i][j]=(j-i+1)dp[i-1][j]+\sum_{k=i-1}^{j-1}dp[i-1][k] \]

前面表示选完\(i-1\)个数已经包含\(j\)
那在\(1\sim j\)中还有\(j-i+1\)个数可以选择填入
否则以前没有选择\(j\)且全部小于\(j\),那么现在选择\(j\)就可以了。
对于一个限制直接让\(dp[i][i]=0\)就可以了
然而这是\(O(n^3)\)的做法,不过后面这一坨前缀和优化就可以做到\(O(n^2)\)
然而如果\(n\)很大,但是\(m\)还是原来的数据范围的话会直接T飞,
但是数据还是很良心的,还是wtcl
同步于牛客博客


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int mod=20000311,N=2011;
int n,m,a[N],dp[N][N];
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans;
}
inline signed mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
signed main(){
    n=iut(),m=iut();
    for (rr int i=1;i<=m;++i) a[i]=iut();
    for (rr int i=1;i<=n;++i) dp[1][i]=1;
    sort(a+1,a+1+m);
    for (rr int i=1,I=1;i<=n;++i){
        rr int sum=dp[i-1][i-1];
        if (i>1) for (rr int j=i;j<=n;++j)
            dp[i][j]=mo(sum,1ll*dp[i-1][j]*(j-i+1)%mod),
            sum=mo(sum,dp[i-1][j]);
        if (a[I]==i) dp[i][i]=0,++I;
    }
    return !printf("%d",dp[n][n]);
}
posted @ 2020-10-10 10:06  lemondinosaur  阅读(86)  评论(0编辑  收藏  举报