很直观的一维DP
状态:f[i]表示将前i个玩具打包的最小费用。
朴素方程:f[i] = min{f[j] + (w[j+1][i] - L) ^ 2} (w[j+1][i] =∑ Ck + i - (j + 1),j < i,k ∈[j+1,i])
但是直接朴素会TLE,所以我们考虑将方程变形,挖掘方程的性质。
令sum[i]= ∑Ck k∈ [1,i]
-> f[i] = min{ f[j] + (sum[i] - sum[j] + i - j - 1 - L) ^ 2 }
令s[i] = sum[i] + i
-> f[i] = min{ f[j] + (s[i] - s[j] - L - 1) ^ 2}
令m = s[i] - l - 1
-> f[i] = min{ f[j] + (m - s[j]) ^ 2 }
-> f[i] = min{ f[j] + m ^ 2 - 2 * m * s[j] + s[j] ^ 2 }
考虑∀决策k,k < i且决策k比决策j更优
->f[k] - 2 * m * s[k] + s[k] ^ 2 + m ^ 2 < f[j] - 2 * m * s[j] + s[j] ^ 2 + m ^ 2
-> f[k] - 2 * m * s[k] + s[k] ^ 2 < f[j] - 2 * m * s[j] + s[j] ^ 2
-> (f[k] + s[k] ^ 2) - (f[j] + s[j] ^ 2)/ (s[k] - s[j]) < 2 * m
这样我们可以令x = s[i],y = f[j] + s[j] ^ 2
则可以将每个决策看作两个点,只要满足上述条件则决策更优,使用一个队列来维护最优决策就可以了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 50000 + 10;
int deq[maxn];
long long c[maxn],S[maxn],f[maxn];
int n,l;
void init()
{
freopen("bzoj1010.in","r",stdin);
freopen("bzoj1010.out","w",stdout);
}
inline double slope(int i,int j)
{
return (double)((f[i] + S[i] * S[i] - f[j] - S[j] * S[j]) / (S[i] - S[j]));
}
void readdata()
{
memset(c,0,sizeof(c));
memset(S,0,sizeof(S));
scanf("%d%d",&n,&l);
for(int i = 1;i <= n;i++)
{
scanf("%lld",&c[i]);
c[i] += c[i-1];S[i] = c[i] + i;
}
}
void solve()
{
memset(f,0,sizeof(f));
int s = 0,e = 0;
for(int i = 1;i <= n;i++)
{
long long m = S[i] - l - 1;
while(s < e && slope(deq[s+1],deq[s]) <= 2 * m)++s;
int j = deq[s];
f[i] = f[j] + (m - S[j]) * (m - S[j]);
while(s < e && slope(deq[e],deq[e-1]) >= slope(i,deq[e]))--e;
deq[++e] = i;
}
printf("%lld\n",f[n]);
}
int main()
{
init();
readdata();
solve();
return 0;
}