codeforces1068——D.Array Without Local Maximums(计数DP+前缀和优化)

原题链接
题意:
给定一个序列,a[i]==-1表示该位无元素。现在要你填充这个序列使得a[i]<=max(a[i+1],a[i-1]),a[0]=a[n+1]=0
(最后一句可以这样理解)
1<=a[i]<=200.求方案数
思路:
计数DP,推了好久,还没想到前缀和优化。
首先是状态表示,因为a[i]的范围只有200,我们不妨来枚举填1~200的每一种情况:dp[i][j][k]表示第i个数填j时并且和第i-1个数的关系为k的方案数,当然这肯定是只有需要填数时才可以转移。
接下来是状态转移,我们考虑a[i-1]的合法性来进行转移:
1.当k=0时,表示a[i]>a[i-1],这时候第i-1个数只能从[1,j-1]里选择,因为a[i-1]<a[i]一定成立了,所以a[i-1]和a[i-2]的关系可以是大于,小于和等于。
2.当k=1时,表示a[i]==a[i-1],这时候只能从dp[i-1][j]转移过来了,同时对于a[i-2]来说,k=0/1/2都成立。
3.当k=2时,表示a[i]<a[i-1],这时候第i-1个数只能从[j+1,200]里选了,同时因为a[i-1]>a[i],所以a[i-1]<=a[i-2]必须满足,即k=1/2。
关于初始化,如果a[1]=-1的话,说明该位可以填充,就dp[1][j][0]=1,我们可以理解为a[0]=0,所以第1个数填j(1<=j<=200)的方案数就是1;如果a[1]原本有数的话,就dp[1][a[1][0]=1.
最后是优化: 上面算法的复杂度是O(200 *200 *n),我们可以对情况1和情况3用前缀和优化。
代码:

const int maxn=1e5+100;
///a[i]<=a[i-1]
///dp[i][j][k]表示第i个数选j和第i-1个数的关系是k的方案数
///k==0 a[i]>a[i-1]  dp[i-1][1~j-1][0/1/2]
///k==1 a[i]==a[i-1] dp[i-1][j][0/1/2]
///k==2 a[i]<a[i-1]  dp[i-1][j+1~200][1/2]
ll dp[maxn][205][3];
ll a[maxn],n;
int main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    if(a[1]!=-1) dp[1][a[1]][0]=1;///该位无需填充
    else///该位需要填充
        for(int i=1;i<=200;i++) dp[1][i][0]=1;
    for(int i=2;i<=n;i++){///枚举当前填充到第几个数了
        ll sum=0;
        for(int j=1;j<=200;j++){
            if(a[i]==-1||a[i]==j) dp[i][j][0]=sum;///可以填充或是该位正好为枚举的数
            sum=(sum+dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][j][2])%mod;
        }
        sum=0;
        for(int j=1;j<=200;j++){
            if(a[i]==-1||a[i]==j)
                dp[i][j][1]=(dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][j][2])%mod;
        }
        for(int j=200;j;j--){
            if(a[i]==-1||a[i]==j) dp[i][j][2]=sum;
            sum=(sum+dp[i-1][j][1]+dp[i-1][j][2])%mod;
        }
    }
    ll res=0;
    for(int i=1;i<=200;i++)
        res=(res+dp[n][i][1]+dp[n][i][2])%mod;
    out(res);
    return 0;
}
posted @ 2020-11-19 19:21  OvO1  阅读(45)  评论(0编辑  收藏  举报