题目描述:
有N个数字,有的数字为Ai(1<=Ai<=200),有的数字为-1,-1表示这个数字不确定。
但是这N个数字满足两个性质。
1:每个数字只能为[1,200]中的一个数字
2:每个数字的旁边两个数字至少有一个是大于等于这个数字的
现在让你求这N个数字的可能出现的方案数
题目分析:
典型的DP。
dp[i][j][0/1/2] 表示第 i 这个位置填 j 数字 并且与它左边的数字的大小关系为 k 的方案数有多少种
0表示i比i-1大,1表示i跟i-1一样大,2表示i比i-1小
dp[i][j][1]=dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][2] 因为i这个数字跟i-1一样大,那么i-1就满足了条件,所以不论i-1与i-2的关系如何,都是符合要求的。
d
p
[
i
]
[
j
]
[
0
]
=
∑
k
=
1
j
−
1
d
p
[
i
−
1
]
[
k
]
[
0
/
1
/
2
]
dp[i][j][0]=\sum_{k=1}^{j-1} dp[i-1][k][0/1/2]
dp[i][j][0]=∑k=1j−1dp[i−1][k][0/1/2]因为i这个数字比i-1的数字大,那么i-1就满足了条件,所以不论i-1与i-2的关系如何,都是符合要求的。
d
p
[
i
]
[
j
]
[
0
]
=
∑
k
=
j
+
1
n
d
p
[
i
−
1
]
[
k
]
[
1
/
2
]
dp[i][j][0]=\sum_{k=j+1}^{n} dp[i-1][k][1/2]
dp[i][j][0]=∑k=j+1ndp[i−1][k][1/2]因为i这个数字比i-1小,那么i-1就不能指望i来满足条件,于是只能转移i-1中已经满足了条件的方案,也就是1/2的方案了。
对于后面两个转移方程,明显一个是前缀,一个是后缀,直接用一个量记录就好了。
我们发现i的状态只与i-1有关系,所以我们可以去掉第一维,用两个数组滚动节省空间。
题目链接:
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
const int maxm=100010;
const int mod=998244353;
const int M=200;
int dp[3][210][3];
int val[maxm];
int n;
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
for(int i=1;i<=200;i++)
{
if((val[1]==-1)||(i==val[1])) dp[1][i][0]=1;
else dp[1][i][0]=0;
}
int pre=1,nex=2;
for(int i=2,now;i<=n;i++)
{
for(int j=1;j<=M;j++)
{
if((val[i]==-1)||(j==val[i])) dp[nex][j][1]=(dp[pre][j][0]+(dp[pre][j][1]+dp[pre][j][2])%mod)%mod;
else dp[nex][j][1]=0;
}
now=0;
for(int j=1;j<=M;j++)
{
if((val[i]==-1)||(j==val[i])) dp[nex][j][0]=now;
else dp[nex][j][0]=0;
now=(now+(dp[pre][j][0]+(dp[pre][j][1]+dp[pre][j][2])%mod)%mod)%mod;
}
now=0;
for(int j=M;j>=1;j--)
{
if((val[i]==-1)||(j==val[i])) dp[nex][j][2]=now;
else dp[nex][j][2]=0;
now=(now+dp[pre][j][1]+dp[pre][j][2])%mod;
}
if(pre==1) pre=2,nex=1;
else pre=1,nex=2;
}
int ans=0;
for(int i=1;i<=200;i++) ans=(ans+(dp[pre][i][1]+dp[pre][i][2])%mod)%mod;
return printf("%lld\n",ans)*0;
}