【BZOJ1925】 [SDOI2010] 地精部落(帶有一堆性質的動態規劃)

點此看題面

大致題意: 問你有多少長度爲nn的數列,它當中每個數字要麼比旁邊兩個數字都小,要麼比旁邊兩個數字都大。


性質

這題應該比較顯然是一道動態規劃題,但剛看到這題時我卻無從下手。

其實,瞭解了關於這種合法數列的幾個性質,這題就不難了。

  • 它具有對稱性。
    • 即如果a1a2...ana_1a_2...a_n爲合法數列,則anan1...a1a_na_{n-1}...a_1也是一個合法數列。
    • 那麼這個性質有什麼作用呢?這就說明,我們要將最後求出的答案2*2。(我就因爲沒寫這個調死了)
  • 我們可以將大小相差11且不相鄰的元素交換。
    • 比如說,我們設在一個合法數列a1a2...ana_1a_2...a_n中,aiaj=1,ij>1|a_i-a_j|=1,|i-j|>1
    • 則交換了aia_iaja_j之後,新數列仍合法。
    • 這個性質應該還是比較顯然的,無需證明吧。
  • 我們可以將山谷與山峯相互轉化。
    • 呃,這句話讀起來的確有點莫名其妙,關鍵在於我語文太差,不知如何形容。
    • 比如說對於一個合法數列a1a2...ana_1a_2...a_n,我們可以將它們每一個元素的值都改成na+1n-a+1,即變成(na1+1)(na2+1)...(nan+1)(n-a_1+1)(n-a_2+1)...(n-a_n+1),可以證明新數列仍然合法,且原來的山谷變成了山峯,原來的山峯變成了山谷。
    • 這個性質有什麼意義呢?它的意義就在於,我們就無需區分是山峯還是山谷,直接進行狀態轉移(反正都可以相互轉化),而不需要再開一維記錄是山峯還是山谷了。
    • 這在之後的狀態轉移中起到了巨大的作用。

如何狀態轉移

在瞭解了以上幾點性質之後,這題的DPDP就不難了。

考慮用fi,jf_{i,j}表示在前ii個數中以jj爲序列尾部,且這是一個山峯時的方案數

接下來,我們要分情況討論:

  • jjj1j-1不相鄰。
    • 在這種情況下,我們可以根據上面的性質得到,交換jjj1j-1是不會影響數列的合法性的。
    • 於是可以將fi,jf_{i,j}fi,j1f_{i,j-1}轉移得來。
  • jjj1j-1相鄰。
    • 既然我們確定jj是山峯,而j1j-1jj相鄰,因此我們可以確定:j1j-1是山谷。
    • 也就是說,我們要求的就是在前i1i-1個數中以j1j-1爲序列尾部,且這是一個山谷時的方案數
    • 或許有些人會考慮再加一個數組gi1,j1g_{i-1,j-1}來表示這個值,但這樣就會使代碼複雜許多。
    • 我們可以回顧上面提到的一個性質:我們可以將山谷與山峯相互轉化,現在它就派上用場了。
    • 根據這個性質,在前i1i-1個數中以j1j-1爲序列尾部,且這是一個山谷時的方案數就等同於在前i1i-1個數中以(i1)(j1)+1(i-1)-(j-1)+1爲序列尾部,且這是一個山峯時的方案數
    • 而這後半句話,其實就等同於fi1,(i1)(j1)+1f_{i-1,(i-1)-(j-1)+1}
    • 綜上所述,我們可以將fi,jf_{i,j}fi1,(i1)(j1)+1f_{i-1,(i-1)-(j-1)+1}轉移得來,化簡一下就是fi1,ij+1f_{i-1,i-j+1}

於是我們就得出了轉移方程:fi,j=fi,j1+fi1,ij+1f_{i,j}=f_{i,j-1}+f_{i-1,i-j+1}


關於內存

這題開O(N2)O(N^2)的數組貌似會MLEMLE能過當我沒說。

於是,我們可以用節約內存的常見方法:滾存

這樣就跑得毫無壓力了。


代碼

#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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章