題解 CF1067A Array Without Local Maximums
阿新 • • 發佈:2021-08-29
大佬們的題解都太深奧了,直接把轉移方程放出來讓其他大佬們感性理解,蒟蒻們很難理解,所以我就寫了一篇讓像我一樣的蒟蒻能看懂的題解
動態規劃三部曲:確定狀態,轉移方程,初始狀態和答案。
——神仙 @akicc
第一步 確定狀態
\(f_{i,j,k}(k\in\{0,1,2\})\)表示第 \(i\) 個數選為 \(j\) 且和前一個數是小於/等於/大於(\(k=0\) 是大於,\(k=1\) 是等於,\(k=2\) 是小於)的關係時的方案數。
第二步 轉移方程
把三種關係分開討論:
- \(k=0\),比上一個大,它的方案數就是上一個數選的比它小的數的方案數和;
- \(k=1\),由於和上一個數相同,它的方案數就是上一個數的方案數和;
- \(k=2\),比上一個小,它的方案數就是上一個數選的比它大的數的方案數和,但是為了防止上一個數比相鄰的數都大,我們要去掉上一個數比上上個數大的方案數。
那麼我們的轉移方程就是:
\(f_{i,j,0}=\sum^{j-1}_{l=1}f_{i-1,l,0}+f_{i-1,l,1}+f_{i-1,l,2}\)
\(f_{i,j,1}=f_{i-1,j,0}+f_{i-1,j,1}+f_{i-1,j,2}\)
\(f_{i,j,2}=\sum^{200}_{l=j+1}f_{i-1,l,1}+f_{i-1,l,2}\)
如果 \(a_i=-1\) 則 \(1\le j\le 200\),否則 \(j=a_i\)
直接求和會超時,我們可以使用字首和優化。
第三步 初始狀態和答案
如果第二個數取得比第一個數小就不符合題目要求了,而第一個數只有一種取法,所以我們讓 \(f_{1,j,0}=1\),就可以讓 \(f_{2,j,2}\) 取不到方案數了!
如果最後一個數比倒數第二個數大,也不符合題意,所以我們在取答案的時候不能取 \(f_{n-1,j,0}\) 。
程式碼
#include <bits/stdc++.h> #define _for(i,a,b) for(int i=a;i<=b;++i) #define for_(i,a,b) for(int i=a;i>=b;--i) #define ll long long using namespace std; const int N=1e5+10,M=998244353; ll n,a[N],f[N][205][3],ans; void pre(){ if(a[1]==-1)_for(i,1,200)f[1][i][0]=1; else f[1][a[1]][0]=1; }void dp(){ _for(i,2,n){ int s=0; _for(j,1,200){ if(a[i]==-1||a[i]==j)f[i][j][0]=s%M,f[i][j][1]=(f[i-1][j][0]+f[i-1][j][1]+f[i-1][j][2])%M; s=(s+f[i-1][j][0]+f[i-1][j][1]+f[i-1][j][2])%M; }s=0; for_(j,200,1){ if(a[i]==-1||a[i]==j)f[i][j][2]=s%M; s=(s+f[i-1][j][1]+f[i-1][j][2])%M; } } }int main(){ scanf("%lld",&n); _for(i,1,n)scanf("%lld",&a[i]); pre(),dp(); _for(i,1,200)ans=(ans+f[n][i][1]+f[n][i][2])%M; printf("%lld",ans); //system("pause"); return 0; }
本文來自部落格園,作者:Keven-He,轉載請註明原文連結:https://www.cnblogs.com/Keven-He/p/TJ-CF1067A.html