1. 程式人生 > 其它 >題解 CF1067A Array Without Local Maximums

題解 CF1067A Array Without Local Maximums

大佬們的題解都太深奧了,直接把轉移方程放出來讓其他大佬們感性理解,蒟蒻們很難理解,所以我就寫了一篇讓像我一樣的蒟蒻能看懂的題解


原題傳送門

動態規劃三部曲:確定狀態,轉移方程,初始狀態和答案。

——神仙 @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