题意:给定一个长度为 n(n≤1e5) 的数组 a[1:n],a[i] 的值域为 1 到 200,且每个数的旁边必须有一个不小于它的数。而有些数字被擦掉了,现在问共有多少种可行的填充方案。
题解:
看到这题第一感觉就dfs暴力搜索,然而时间复杂度显然不行,因此考虑dp。
考虑 dp[i][x][0,1,2] 的表示的状态为已经确定了第 i 个数字为 x,而 0,1,2 分别表示第 i−1 个数 w 是小于、等于或大于 x。其存储的值是其状态下的方案数。
那么就有状态转移方程(当然这是建立在第 i+1 位可以填 y 的前提下的):
dp[i+1][y][0]=∑x=1y−1dp[i][x][0,1,2]dp[i+1][y][1]=dp[i][y][0,1,2]dp[i+1][y][2]=∑x=y+1200dp[i][x][1,2]
边界条件,考虑到第 1 个数左边是空的,相当于左边是一个更小的数,因此有(依然是建立在第一位能填入 x 的前提下):
dp[1][x][0]=1
dp[1][x][1]=0
dp[1][x][2]=0
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e5+5;
int n,a[maxn];
ll sum,dp[maxn][203][3];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int x=1;x<=200;x++)
{
if(a[1]!=-1 && a[1]!=x) dp[1][x][0]=dp[1][x][1]=dp[1][x][2]=0;
else dp[1][x][0]=1, dp[1][x][1]=dp[1][x][2]=0;
}
for(int i=2;i<=n;i++)
{
sum=0;
for(int y=1;y<=200;y++)
{
if(a[i]!=-1 && a[i]!=y) dp[i][y][0]=0;
else dp[i][y][0]=sum;
sum+=(dp[i-1][y][0]+dp[i-1][y][1]+dp[i-1][y][2])%mod;
sum%=mod;
}
for(int y=1;y<=200;y++)
{
if(a[i]!=-1 && a[i]!=y) dp[i][y][1]=0;
else dp[i][y][1]=(dp[i-1][y][0]+dp[i-1][y][1]+dp[i-1][y][2])%mod;
}
sum=0;
for(int y=200;y>=1;y--)
{
if(a[i]!=-1 && a[i]!=y) dp[i][y][2]=0;
else dp[i][y][2]=sum;
sum+=(dp[i-1][y][1]+dp[i-1][y][2])%mod;
sum%=mod;
}
}
ll ans=0;
for(int x=1;x<=200;x++) ans+=(dp[n][x][1]+dp[n][x][2])%mod, ans%=mod;
cout<<ans<<endl;
}