E的話一眼dp,然後觀察一下方程,\(f[i][j]表示前i個位置已經選了長度為j的區間,且第i個位置已經被選上時,能夠獲得的最大值\)
這樣的dp是\(O(n^2k)\)的,而\(1\leq k\leq n\leq 3000\),我們需要最佳化到\(O(n^2log(n))\)這個級別。
觀察dp,考慮最佳化,正常情況下,最佳化的部分都是列舉k的部分,我們對每次轉移分開考慮,假如我不需要再去列舉k,而是直接透過資料結構查詢得到就可以了。
發現\(f[i-k][j-k]\)是一個很有規律的東西,轉化為圖形,就是在dp數值組成的表裡面斜率為1的直線透過的點。而這個很明顯是可以維護的,問題就在後面的\(calc\)的代價函式中
我們現在的\(f[i-k][j-k]\)可以被單獨維護,導致我們必須列舉k的東西是calc中含有k的部分,假如這部分不存在,那麼其實直接維護\(f[i-k][j-k]和後面剩下部分的\)的\(max\)即可,對於絕對值也是隨便處理,因為這個值其實是固定的。
而含有和k相關的部分,這導致先前使用過的,已經維護好的\(f[i-k][j-k]\)的最大值並不能直接套用,我們必須考慮新的一位和前面部分的結合而形成的絕對值的影響。
假如把絕對值去掉,是否能做呢?
可以。而且很好做。觀察發現,其實\(calc\)函式中含有\(k\)的部分是和\(f[i-k][j-k]\)繫結的,這個其實從題目中對價值的描述中可以更好的看出來。也就是說,我們其實是直接維護了這個式子含有k的所有部分一同的最大值,這個很好,因為剩下的只和\(i,j\)相關的部分的值是確定的,也就是說我們需要的東西就直接\(O(1)\)維護好了。
而這個只是沒有絕對值的情況,絕對值這個東西也不算難處理,直接分類討論,拆開,我們兩個絕對值一共4中情況。而事實上也不用分開維護,仔細考慮一下,加上絕對值只會讓答案變大,所以直接取max,根本就可能取到不合法的情況的值。否則沒法維護。這不是一個普通的偏序。這一點很巧妙。
不錯的dp,分析這個代價函式的內容是情理之中,而這個代價函式中和\(f[i-k][j-k]\)強相關的部分是要從題目中理解得到的,這算是一個小難點。其實拆開絕對值的操作也是很合理的,想要最佳化這個dp,無非就是從狀態和轉移兩方面下手,所有的dp都是這麼最佳化的,而狀態的最佳化其實是對應了前兩個迴圈,這很顯然很難辦到,那就是加速轉移。想要加速轉移,那拆開這個絕對值就是必要的思路。
這邊就給我提供了一個很好的思路,先假設絕對值不存在,然後再考慮。
還算簡單的一個dp最佳化吧。這題居然能有2500。那9062E2居然只有2600。這兩題之間的難度差距真心挺大。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
ll n,K,a[3021],b[3021],f[3021][3021];
ll Max[3021][3021][5];
ll s1[5]={0,+1,-1,+1,-1},s2[5]={0,-1,-1,+1,+1};
int main()
{
ll T=read();
while(T--)
{
n=read(),K=read();
for(ll i=1;i<=n;i++)a[i]=read();
for(ll i=1;i<=n;i++)b[i]=read();
for(ll i=0;i<=n;i++)
{
for(ll j=0;j<=K;j++)
{
f[i][j]=0;
for(ll k=1;k<=4;k++)
Max[i][j][k]=-(1LL<<60);
}
}
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=K;j++)
{
f[i][j]=f[i-1][j];
for(ll k=1;k<=4;k++)
Max[i][j][k]=max(Max[i-1][j-1][k],f[i-1][j-1]+a[i]*s1[k]+b[i]*s2[k]);
for(ll k=1;k<=4;k++)
f[i][j]=max(f[i][j],Max[i][j][k]-a[i]*s2[k]-b[i]*s1[k]);
}
}
cout<<f[n][K]<<endl;
}
return 0;
}