背景:
我连这都不会…
题目传送门:
(这是一道例题)http://acm.hdu.edu.cn/showproblem.php?pid=3507
题意:
给定
n
,
m
n,m
n,m,接下来有
n
n
n个数
a
i
a_i
ai,表示每一个单词的权值。现在你需要将这些单词分行,每一行的权值的计算公式是:
(
∑
i
=
1
k
a
i
)
2
+
m
(\sum_{i=1}^{k}a_i)^2+m
(∑i=1kai)2+m,现在求最小的权值和。
思路:
显然我们能想到常规做法。
设
f
i
f_i
fi表示从第
1
1
1个单词到第
i
i
i个单词的权值和的最小值。
设
s
u
m
k
=
∑
i
=
1
k
a
i
sum_k=\sum_{i=1}^{k}a_i
sumk=∑i=1kai
显然得到转移方程:
f
i
=
min
j
=
1
i
{
f
j
+
(
s
u
m
i
−
s
u
m
j
)
2
}
f_i=\min_{j=1}^{i}\{f_j+(sum_{i}-sum_j)^2\}
fi=minj=1i{fj+(sumi−sumj)2}。
时间复杂度:
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)。
可是我们过不了怎么办?
考虑
i
i
i从
j
,
k
j,k
j,k转移过来的情况,且从
j
j
j转移更优,
j
>
k
j>k
j>k。
可以得到不等式:
f
j
+
(
s
u
m
i
−
s
u
m
j
)
2
<
f
k
+
(
s
u
m
i
−
s
u
m
k
)
2
f_j+(sum_i-sum_j)^2<f_k+(sum_i-sum_k)^2
fj+(sumi−sumj)2<fk+(sumi−sumk)2
展开括号,得:
f
j
+
s
u
m
i
2
+
s
u
m
j
2
−
2
s
u
m
i
s
u
m
j
<
f
k
+
s
u
m
i
2
+
s
u
m
k
2
−
2
s
u
m
i
s
u
m
k
f_j+sum_i^2+sum_j^2-2sum_isum_j<f_k+sum_i^2+sum_k^2-2sum_isum_k
fj+sumi2+sumj2−2sumisumj<fk+sumi2+sumk2−2sumisumk
消去同类项,得:
f
j
+
s
u
m
j
2
−
2
s
u
m
i
s
u
m
j
<
f
k
+
s
u
m
k
2
−
2
s
u
m
i
s
u
m
k
f_j+sum_j^2-2sum_isum_j<f_k+sum_k^2-2sum_isum_k
fj+sumj2−2sumisumj<fk+sumk2−2sumisumk
再移项,得:
f
j
−
f
k
+
s
u
m
j
2
−
s
u
m
k
2
<
+
2
s
u
m
i
s
u
m
j
−
2
s
u
m
i
s
u
m
k
f_j-f_k+sum_j^2-sum_k^2<+2sum_isum_j-2sum_isum_k
fj−fk+sumj2−sumk2<+2sumisumj−2sumisumk
提取同类项,得:
f
j
−
f
k
+
s
u
m
j
2
−
s
u
m
k
2
<
2
s
u
m
i
(
s
u
m
j
−
s
u
m
k
)
f_j-f_k+sum_j^2-sum_k^2<2sum_i(sum_j-sum_k)
fj−fk+sumj2−sumk2<2sumi(sumj−sumk)
再移项,得:
f
j
−
f
k
+
s
u
m
j
2
−
s
u
m
k
2
s
u
m
j
−
s
u
m
k
<
2
s
u
m
i
+
1
\frac{f_j-f_k+sum_j^2-sum_k^2}{sum_j-sum_k}<2sum_{i+1}
sumj−sumkfj−fk+sumj2−sumk2<2sumi+1
设
T
i
=
f
i
+
s
u
m
i
2
T_i=f_i+sum_i^2
Ti=fi+sumi2,得:
T
j
−
T
k
s
u
m
j
−
s
u
m
k
<
2
s
u
m
i
+
1
\frac{T_j-T_k}{sum_j-sum_k}<2sum_{i+1}
sumj−sumkTj−Tk<2sumi+1
结论:也就是说若存在
j
>
k
j>k
j>k且
T
j
−
T
k
s
u
m
j
−
s
u
m
k
<
2
s
u
m
i
\frac{T_j-T_k}{sum_j-sum_k}<2sum_i
sumj−sumkTj−Tk<2sumi,那么从
j
j
j转移更优。
p s : ps: ps:联想一下数学课本中的 k = y 2 − y 1 x 2 − x 1 k=\frac{y_2-y_1}{x_2-x_1} k=x2−x1y2−y1,发现左边的式子好像斜率啊。斜率优化就是这么来的。
那么有什么用呢?
因为
T
T
T是由
f
f
f和
s
u
m
sum
sum得到的,
s
u
m
sum
sum是固定的,所以我们求完了
f
f
f,就可以得到
T
T
T。
如图,从我们的斜率推导中我们可以以
s
u
m
sum
sum作为
x
x
x轴,
T
T
T作为
y
y
y轴建立坐标系,对应的点代表
f
f
f的值。
我们在求解
f
n
o
w
f_{now}
fnow时,假设可选
i
,
j
,
k
i,j,k
i,j,k三个点转移,且如上图所示。
由于
k
k
j
>
k
j
i
k_{kj}>k_{ji}
kkj>kji,所以
T
j
−
T
k
s
u
m
j
−
s
u
m
k
>
T
i
−
T
j
s
u
m
i
−
s
u
m
j
\frac{T_j-T_k}{sum_j-sum_k}>\frac{T_i-T_j}{sum_i-sum_j}
sumj−sumkTj−Tk>sumi−sumjTi−Tj
由上面的结论可知两边的式子要与
2
s
u
m
n
o
w
2sum_{now}
2sumnow比较。
就存在三种可能:
[
1
]
:
[1]:
[1]:
T
j
−
T
k
s
u
m
j
−
s
u
m
k
>
T
i
−
T
j
s
u
m
i
−
s
u
m
j
>
2
s
u
m
n
o
w
\frac{T_j-T_k}{sum_j-sum_k}>\frac{T_i-T_j}{sum_i-sum_j}>2sum_{now}
sumj−sumkTj−Tk>sumi−sumjTi−Tj>2sumnow
由上面的结论可知此时
j
j
j比
i
i
i优,但
k
k
k比
j
j
j优,所以选择
k
k
k转移。
[
2
]
:
[2]:
[2]:
T
j
−
T
k
s
u
m
j
−
s
u
m
k
>
2
s
u
m
n
o
w
>
T
i
−
T
j
s
u
m
i
−
s
u
m
j
\frac{T_j-T_k}{sum_j-sum_k}>2sum_{now}>\frac{T_i-T_j}{sum_i-sum_j}
sumj−sumkTj−Tk>2sumnow>sumi−sumjTi−Tj
由上面的结论可知此时
k
k
k比
j
j
j优,
i
i
i比
j
j
j优,所以选择
i
i
i或
k
k
k转移。
[
3
]
:
[3]:
[3]:
2
s
u
m
n
o
w
>
T
j
−
T
k
s
u
m
j
−
s
u
m
k
>
T
i
−
T
j
s
u
m
i
−
s
u
m
j
2sum_{now}>\frac{T_j-T_k}{sum_j-sum_k}>\frac{T_i-T_j}{sum_i-sum_j}
2sumnow>sumj−sumkTj−Tk>sumi−sumjTi−Tj
由上面的结论可知此时
i
i
i比
j
j
j优,
j
j
j比
k
k
k优,所以选择
i
i
i转移。
综上所述,我们一定不会选择从
j
j
j转移。
我们维护一个这样的类似凸包且斜率单调递增的东西:
假设
k
j
i
>
2
s
u
m
n
o
w
k_{ji}>2sum_{now}
kji>2sumnow且
k
k
j
<
2
s
u
m
n
o
w
k_{kj}<2sum_{now}
kkj<2sumnow,我们可以从上面的结论得出
j
j
j点比所有比
k
k
k小的点都优,比所有比
i
i
i大的也优。所以我们二分查找斜率比
2
s
u
m
n
o
w
2sum_{now}
2sumnow小的编号最大的点,就是最优的转移点。由于
s
u
m
sum
sum也有单调性,我们直接维护一个单调队列就可以了。
维护时,若队列前面的点不满足结论,则删掉;若后面的点不满足斜率单调递增,则删掉。
总结:
敲黑板。
总的来说,首先根据题意列出
d
p
dp
dp方程,找出其中有单调性的元素,根据状态列出斜率方程(不等式),判断是上凸壳还是下凸壳,相应的用单调队列等方式解决即可。
我还是做了一些题,大家可以在我的标签中搜索,欢迎指出错误。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int n,m;
LL a[1000010],sum[1000010],f[1000010];
int que[1000010];
LL calc1(int x,int y)
{
return sum[y]-sum[x];
}
LL calc2(int x,int y)
{
return (f[y]+sum[y]*sum[y])-(f[x]+sum[x]*sum[x]);
}
int main()
{
while(scanf("%d %d",&n,&m)!=EOF)
{
f[0]=sum[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
int head=1,tail=1;
que[1]=0;
for(int i=1;i<=n;i++)
{
while(head<tail&&calc2(que[head],que[head+1])<=(LL)2*sum[i]*calc1(que[head],que[head+1])) head++;
/*原来形如calc2(x,y)/calc1(x,y)<=2*sum[i],移项(使得没有小数计算),得到上面的式子*/
f[i]=f[que[head]]+calc1(que[head],i)*calc1(que[head],i)+m;
while(head<tail&&calc2(que[tail],i)*calc1(que[tail-1],que[tail])<=calc2(que[tail-1],que[tail])*calc1(que[tail],i)) tail--;
/*原来形如calc2(x1,y1)/calc2(x1,y1)<=calc2(x2,y2)*calc1(x2,y2)/,移项(使得没有小数计算),得到上面的式子*/
que[++tail]=i;
}
printf("%lld\n",f[n]);
}
}