一个挺神仙的区间 DP,细想起来又其实是非常平凡的,毕竟每一步撕烤都有迹可循。听说原来好像是个黑题/jy,0rz〇rz
先来探讨一下方案长什么样子。取了一个区间后,以后取的区间要么与该区间无交,要么包含它(真正取的元素使它的补)。所以方案呈一个以包含关系为纽带的树形结构,根是 ([1,n]),每个区间取的元素为它减去它的若干直接包含的区间。没有相交,这就很适合转化为子问题。
考虑在初始的全局局面 ([1,n]) 上做出一步决策。看起来比较难:可决策的只有若干直接包含的区间,以及剩下的元素。枚举最后一个直接包含的区间的左右端点的话,其内部可以归约到子问题,但是外部变成了 ([1,n]-I),变成一个四不像的东西。要按照此思路一直决策下去的话,会得到 (2^n) 个状态,封闭不住直接爆炸。于是只能考虑决策后者。
但是后者看上去更难决策,这枚举都没法枚举。但是注意到剩下来的元素的代价至于 min 和 max 有关,于是我们可以直接枚举 min 和 max(以下认为值域大小也是 (n),离散化即可)!先考虑确实剩下来至少一个元素的情况,这样转移所需要的信息是 ([1,n]) 中选掉一些直接包含的区间,使得剩下来元素全部 (in[mn,mx]),最小代价(代价其实就是每个直接包含的区间作为子问题的答案之和)为多少。这看起来就是个挺常规的区间 DP 了。以及直接包含的区间作为子问题的答案需要用到所有区间 ([l,r]) 答案,此时我们正式设计状态:(dp_{l,r}) 表示子问题 ([l,r]) 的答案,(f_{l,r,i,j}) 表示 ([l,r]) 中只剩 ([i,j]) 内元素的最小代价。目标 (dp_{1,n}),转移的话他们两个互相转移,相辅相成,缺一不可。
(f) 的转移的话,对 (r) 进行决策,分两种情况:若 (a_rin[i,j]),则 (r) 可以剩下,(r) 往左移一格;(r) 不剩下而作为直接包含的区间的元素的话,寻找左端点 (k),转移到 (f_{l,k-1,i,j}+dp_{k,r})。(dp) 的转移,当剩下至少一个元素时,枚举 min 和 max (i,j),转移到 (f_{l,r,i,j}+A+B(i-j)^2)。当一个元素都不剩的时候,此时转移是容易的,一个复杂的想法是另搞一个 DP 专门表示一个元素都不剩的情况,然后枚举 (r) 所在直接包含区间的左端点进行转移,然后再给 (dp_{l,r}) chkmin。但其实可以直接在原 (dp) 上枚举断点 (i),这样虽然会误统计到 ([l,i-1]) 有元素剩下的情况,但无伤大雅,第一该统计的情况一个都没漏,第二多统计的情况并没有超出所有要统计情况的范围,跟据 max 的可叠加性,这不影响答案。
最后有个小小的环状转移需要考虑一下:(f_{l,r,i,j}) 直接转移到 (dp_{l,r}),(dp_{l,r}) 转移到 (f_{l,r,i,j}+A+B(i-j)^2)。注意到当 (f_{l,r,i,j}) 直接 chkmin (dp_{l,r}) 时,情况是一个都不剩,这种情况并不需要被 (dp_{l,r}) 此时转移时考虑。之所以 (f) 要进行这一步更新,是因为保证 ([l,r]) 的超区间的 (f) 值无误。所以我们可以先放弃 (f) 的这一步更新,把 (dp) 搞完之后再将 (f) 的这一步补上。
总复杂度 (mathrm O!left(n^5 ight))。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=60;
int sq(int x){return x*x;}
int n;
int A,B;
int a[N],b[N];
vector<int> nums;
void discrete(){
sort(nums.begin(),nums.end());
nums.resize(unique(nums.begin(),nums.end())-nums.begin());
for(int i=1;i<=n;i++)b[i]=lower_bound(nums.begin(),nums.end(),a[i])-nums.begin()+1;
}
int f[N][N][N][N];
int dp[N][N],mn[N][N],mx[N][N];
signed main(){
cin>>n>>A>>B;
for(int i=1;i<=n;i++)cin>>a[i],nums.pb(a[i]);
discrete();
for(int l=n;l;l--)for(int r=l;r<=n;r++){
mn[l][r]=inf;mx[l][r]=-inf;
for(int i=l;i<=r;i++)mn[l][r]=min(mn[l][r],a[i]),mx[l][r]=max(mx[l][r],a[i]);
}
for(int l=n;l;l--)for(int r=l;r<=n;r++){
for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++){
f[l][r][i][j]=inf;
if(i<=b[r]&&b[r]<=j)f[l][r][i][j]=min(f[l][r][i][j],f[l][r-1][i][j]);
for(int k=l+1;k<=r;k++)f[l][r][i][j]=min(f[l][r][i][j],f[l][k-1][i][j]+dp[k][r]);
}
dp[l][r]=inf;
for(int i=l;i<=r;i++)dp[l][r]=min(dp[l][r],dp[l][i-1]+A+B*sq(mx[i][r]-mn[i][r]));
for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++)dp[l][r]=min(dp[l][r],f[l][r][i][j]+A+B*sq(nums[i-1]-nums[j-1]));
for(int i=1;i<=nums.size();i++)for(int j=i;j<=nums.size();j++)f[l][r][i][j]=min(f[l][r][i][j],dp[l][r]);
}
cout<<dp[1][n];
return 0;
}