bananahill(NOIP模擬賽Round 8)
題目描述
香蕉川由座香蕉山組成,第i座山有它的高度。小Z準備從左到右爬這裏的恰好座香蕉山,但他不希望山的高度起伏太大,太過顛簸,會讓本就體育不好的他過於勞累。所以他定義了爬山的勞累度是所有爬的相鄰的兩座山的高度差的絕對值之和。小Z希望他勞累值最小,所以他想問這個勞累值最小能是多少?
輸入輸出格式
輸入格式:
第一行兩個整數,表示有座山且他準備爬其中恰好座山。
第二行個數,分別給出每座山的高度。
輸出格式:
輸出一個整數,表示最小的勞累值。
說明
樣例中,小Z可以選擇爬1.2.3三座山或者2.3.4三座山,勞累值為1+1=2
對於50%的數據,
對於100%的數據,
————————————————我是分割線——————————————————
好吧,我們切入正題,這題很顯然就是一道DP題(這不是廢話。。)
那麽問題就在於我們能否寫出O(n^2)或者是O(n^2logn)的DP呢?
如果我們用暴力顯然是不行的。
因為我們如果假設f[i][j]表示爬了i座山,最後一座山為j,那麽我們顯然需要一個循環來枚舉i,同樣,也要枚舉j,
但是由於我們要將DP方程向下推,我們還要枚舉k(j+1<=k<=n)
這樣就是O(n^3)期望得分50分
可是我們想做出O(n^2)DP的話,幾乎是不可能的。
那麽我們如果退而求其次,自然就是O(n^2logn),自然想到線段樹。
我們可以通過線段樹維護最值來優化DP
這道題目中最煩的就是絕對值
因為如果是絕對值就是說明有2種情況,我們無法在一棵線段樹上處理。
所以對於f[i][j],我們自然列出DP方程。
f[i][j]=min(min(f[i-1][k]+num[k]-num[j](j<=k<=n)),min(f[i-1][k]-num[k]+num[j](1<=k<=j)))
然後我們就可以用兩顆線段樹優化min中的2個數啦!
下面貼代碼
#include<cstdio> #include<cstring> #define ls k<<1 #define rs k<<1|1 #define M (1<<10) #define MN 2005 #define min(a,b) ((a)<(b)?(a):(b)) #defineinf 0x3f3f3f3f using namespace std; int n,m; int qmin[M<<1],qmax[M<<1]; int f[MN][MN],num[MN]; void update(int k,int val1,int val2) { k+=M,qmin[k]=min(qmin[k],val1),qmax[k]=min(qmax[k],val2); for(k>>=1;k;k>>=1)qmin[k]=min(qmin[ls],qmin[rs]),qmax[k]=min(qmax[ls],qmax[rs]); } int queh(int k) { int qaq=inf; for(k+=M-1;k!=1;k>>=1) if(~k&1)qaq=min(qaq,qmax[k^1]); return qaq; } int quel(int k) { int qaq=inf; for(k+=M+1;k!=1;k>>=1) if(k&1)qaq=min(qaq,qmin[k^1]); return qaq; } int main(){ memset(qmin,0x3f,sizeof(qmin)); memset(qmax,0x3f,sizeof(qmax)); memset(f,0x3f,sizeof(f)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&num[i]),f[1][i]=0; for(int i=2;i<=m;i++) { for(int j=1;j<i;j++)update(num[j],f[i-1][j]-num[j],f[i-1][j]+num[j]); for(int j=i;j<=n;j++) { f[i][j]=min(queh(num[j])-num[j],quel(num[j])+num[j]); update(num[j],f[i-1][j]-num[j],f[i-1][j]+num[j]); } memset(qmin,0x3f,sizeof(qmin)); memset(qmax,0x3f,sizeof(qmax)); } int ans=inf; for(int i=m;i<=n;i++) ans=min(ans,f[m][i]); printf("%d\n",ans); }
bananahill(NOIP模擬賽Round 8)