给定N(50000),L(1e7),一个长为N的数列C(1e7),要将数列分成若干段(斜率dp全是分段?)。
每段的代价为
(
(
Σ
C
i
)
+
(
段
长
−
1
)
−
L
)
2
((\Sigma C_i)+(段长-1)-L)^2
((ΣCi)+(段长−1)−L)2,求最小的总代价。
设
d
i
d_i
di表示把前
i
i
i个数分好段的最大价值,
d
0
=
0
d_0=0
d0=0;
d
i
=
m
i
n
{
d
j
+
(
s
i
−
s
j
−
1
−
L
)
2
}
,
0
<
=
j
<
i
d_i=min\{d_j+(s_i-s_j-1-L)^2\},0<=j<i
di=min{dj+(si−sj−1−L)2},0<=j<i,其中
s
i
s_i
si是
C
i
+
1
C_i+1
Ci+1的前缀和;
整理得:
d
i
=
(
s
i
−
1
−
L
)
2
+
m
i
n
{
d
j
+
s
j
2
−
2
s
j
(
s
i
−
1
−
L
)
}
d_i=(s_i-1-L)^2+min\{d_j+s_j^2-2s_j(s_i-1-L)\}
di=(si−1−L)2+min{dj+sj2−2sj(si−1−L)}
设
j
<
k
<
i
j<k<i
j<k<i,那么
i
i
i从
k
k
k转移要比从
j
j
j转移更优等价于
(
d
k
+
s
k
2
)
−
(
d
j
+
s
j
2
)
2
s
k
−
2
s
j
<
s
i
−
1
−
L
\frac{(d_k+s_k^2)-(d_j+s_j^2)}{2s_k-2s_j}<s_i-1-L
2sk−2sj(dk+sk2)−(dj+sj2)<si−1−L
使用斜率优化,
y
i
=
d
i
+
s
i
2
,
x
i
=
2
s
i
y_i=d_i+s_i^2,x_i=2s_i
yi=di+si2,xi=2si,目标斜率是
s
i
−
1
−
L
s_i-1-L
si−1−L。
维护下凸包,相邻斜率递增,目标斜率单调递增,双端队列。
因为L开成局部变量以及队列默认元素写成了1,debug了45分钟,我服了。
这题和前面的题基本一样。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 50016, MOD = 1000000007;
ll n, L, sum[M], dp[M];
int q[M], hd, tl;
inline ll subx(int j, int k)
{
return 2*(sum[k]-sum[j]);
}
inline ll suby(int j, int k)
{
ll yj = dp[j]+sum[j]*sum[j];
ll yk = dp[k]+sum[k]*sum[k];
return yk-yj;
}
inline ll cal(int i, int j)
{
return dp[j] + (sum[i]-sum[j]-1-L)*(sum[i]-sum[j]-1-L);
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
n = read(), L = read();
for(int i=1; i<=n; ++i)
sum[i] = sum[i-1] + read() + 1;
hd = tl = 0; q[tl++] = 0;
for(int i=1; i<=n; ++i)
{
while(tl-hd>=2 && suby(q[hd], q[hd+1])<=subx(q[hd],q[hd+1])*(sum[i]-1-L)) ++hd;
dp[i] = cal(i, q[hd]);
while(tl-hd>=2 &&
suby(q[tl-2],q[tl-1])*subx(q[tl-1],i)>=subx(q[tl-2],q[tl-1])*suby(q[tl-1],i)
) --tl;
q[tl++] = i;
}
cout << dp[n] << endl;
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}