原题链接
题意:
给定一个序列,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; }