大致題意: 問你有多少長度爲的數列,它當中每個數字要麼比旁邊兩個數字都小,要麼比旁邊兩個數字都大。
性質
這題應該比較顯然是一道動態規劃題,但剛看到這題時我卻無從下手。
其實,瞭解了關於這種合法數列的幾個性質,這題就不難了。
- 它具有對稱性。
- 即如果爲合法數列,則也是一個合法數列。
- 那麼這個性質有什麼作用呢?這就說明,我們要將最後求出的答案。(我就因爲沒寫這個調死了)
- 我們可以將大小相差且不相鄰的元素交換。
- 比如說,我們設在一個合法數列中,。
- 則交換了與之後,新數列仍合法。
- 這個性質應該還是比較顯然的,無需證明吧。
- 我們可以將山谷與山峯相互轉化。
呃,這句話讀起來的確有點莫名其妙,關鍵在於我語文太差,不知如何形容。- 比如說對於一個合法數列,我們可以將它們每一個元素的值都改成,即變成,可以證明新數列仍然合法,且原來的山谷變成了山峯,原來的山峯變成了山谷。
- 這個性質有什麼意義呢?它的意義就在於,我們就無需區分是山峯還是山谷,直接進行狀態轉移(反正都可以相互轉化),而不需要再開一維記錄是山峯還是山谷了。
- 這在之後的狀態轉移中起到了巨大的作用。
如何狀態轉移
在瞭解了以上幾點性質之後,這題的就不難了。
考慮用表示在前個數中以爲序列尾部,且這是一個山峯時的方案數。
接下來,我們要分情況討論:
- 與不相鄰。
- 在這種情況下,我們可以根據上面的性質得到,交換與是不會影響數列的合法性的。
- 於是可以將從轉移得來。
- 與相鄰。
- 既然我們確定是山峯,而與相鄰,因此我們可以確定:是山谷。
- 也就是說,我們要求的就是在前個數中以爲序列尾部,且這是一個山谷時的方案數。
- 或許有些人會考慮再加一個數組來表示這個值,但這樣就會使代碼複雜許多。
- 我們可以回顧上面提到的一個性質:我們可以將山谷與山峯相互轉化,現在它就派上用場了。
- 根據這個性質,在前個數中以爲序列尾部,且這是一個山谷時的方案數就等同於在前個數中以爲序列尾部,且這是一個山峯時的方案數。
- 而這後半句話,其實就等同於。
- 綜上所述,我們可以將從轉移得來,化簡一下就是。
於是我們就得出了轉移方程:。
關於內存
這題開的數組貌似會。能過當我沒說。
於是,我們可以用節約內存的常見方法:滾存。
這樣就跑得毫無壓力了。
代碼
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 4200
using namespace std;
int n,MOD,f[2][N+5];
class FIO
{
private:
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
public:
FIO() {FinNow=FinEnd=Fin;}
inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
inline void read_char(char &x) {while(isspace(x=tc()));}
inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
inline void write_char(char x) {pc(x);}
inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
int main()
{
register int i,j,ans=0;
for(F.read(n),F.read(MOD),f[0][2]=1,i=3;i<=n;++i)
for(j=2;j<=i;++j) f[i&1][j]=(f[i&1][j-1]+f[(i^1)&1][i-j+1])%MOD;//狀態轉移
for(i=2;i<=n;++i) Inc(ans,f[n&1][i]);//統計答案
return F.write((ans<<1)%MOD),F.end(),0;//輸出,記得將答案*2
}