背景:
我连这都不会…
题目传送门:
(这是一道例题)http://acm.hdu.edu.cn/showproblem.php?pid=3507
题意:
给定
,接下来有
个数
,表示每一个单词的权值。现在你需要将这些单词分行,每一行的权值的计算公式是:
,现在求最小的权值和。
思路:
显然我们能想到常规做法。
设
表示从第
个单词到第
个单词的权值和的最小值。
设
显然得到转移方程:
。
时间复杂度:
。
可是我们过不了怎么办?
考虑
从
转移过来的情况,且从
转移更优,
。
可以得到不等式:
展开括号,得:
消去同类项,得:
再移项,得:
提取同类项,得:
再移项,得:
设
,得:
结论:也就是说若存在
且
,那么从
转移更优。
联想一下数学课本中的 ,发现左边的式子好像斜率啊。斜率优化就是这么来的。
那么有什么用呢?
因为
是由
和
得到的,
是固定的,所以我们求完了
,就可以得到
。
如图,从我们的斜率推导中我们可以以
作为
轴,
作为
轴建立坐标系,对应的点代表
的值。
我们在求解
时,假设可选
三个点转移,且如上图所示。
由于
,所以
由上面的结论可知两边的式子要与
比较。
就存在三种可能:
由上面的结论可知此时
比
优,但
比
优,所以选择
转移。
由上面的结论可知此时
比
优,
比
优,所以选择
或
转移。
由上面的结论可知此时
比
优,
比
优,所以选择
转移。
综上所述,我们一定不会选择从
转移。
我们维护一个这样的类似凸包且斜率单调递增的东西:
假设
且
,我们可以从上面的结论得出
点比所有比
小的点都优,比所有比
大的也优。所以我们二分查找斜率比
小的编号最大的点,就是最优的转移点。由于
也有单调性,我们直接维护一个单调队列就可以了。
维护时,若队列前面的点不满足结论,则删掉;若后面的点不满足斜率单调递增,则删掉。
总结:
敲黑板。
总的来说,首先根据题意列出
方程,找出其中有单调性的元素,根据状态列出斜率方程(不等式),判断是上凸壳还是下凸壳,相应的用单调队列等方式解决即可。
我还是做了一些题,大家可以在我的标签中搜索,欢迎指出错误。
代码:
#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]);
}
}