题目
将n个元素进行分组,每个元素都有一个权值w[i],每组的元素必须是一段连续的元素,将i~j 这一组组带来的费用是 (∑jk=iw[k]−L)2 ( ∑ k = i j w [ k ] − L ) 2 ,求最小费用
分析
老子终于写了一道斜率优化啦!!!!
先说一下斜率优化的一般步骤:
1. 推出DP方程,大概是f[i=min{f[j]+g(i,j)}的形式,max也是可以的
2. 令
k<j<i
k
<
j
<
i
,看j在什么时候比K更优,使劲化简,用各种方法替代啊什么的,最终弄成一个
y(j)−y(k)x(j)−x(k)>=s(i)
y
(
j
)
−
y
(
k
)
x
(
j
)
−
x
(
k
)
>=
s
(
i
)
的形式,注意x(),y(),是两个函数,s也是函数。
3. 看看x()有没有单调性,确定用平衡树维护还是用单调队列,然后画几个例子看一下是维护上凸包还是下凸包,列出一个凸包,如果答案在凸包上而不是在图标两端一般就说明是对的。
4. 用一个队列维护凸包,一般来说对于有些单调性的情况,一个点一旦失效就没有用了,所以实际上像是个单调队列;如果x(),y()都没有单调性,考虑有set维护凸包;如果s()也没有单调性,那么就用三分/set进行查找。
注意:
1. 公式必须认认真真推导,因为你会懒得去手算样例。
2. 对拍很重要,小数据经常随便乱维护都是对的。
3. 维护凸包的时候一般要保证至少还有两个元素。
4. 注意维护凸包用叉积还是斜率,可以用斜率就用斜率无妨,但是存在没有斜率或者太大的时候就要小心了。
5. 我觉得x或者y应该是可以换的,只需要有一个有单调性就可以了。
说罢,小生决定去找一个动态维护凸包的情况,然后发现了一点新天地(滑稽)。
然后发现有一种神奇的方式,CDQ分治!!!
就是说递归解决左边的,你就可以肆无忌惮地把左边排序然后构造一个凸包出来啦!就可以去更新一波右边的答案。
这样的CDQ分治会使得一般有单调性的从O(
n
n
)变成O()
但是,对于没有单调性的,如果你每次重新排序x,那么的确变成了O(
nlog2n
n
l
o
g
2
n
),不过既然在处理的时候按照x拍好序了,那么归并即可,还是O(
nlogn
n
l
o
g
n
)
最后,如果s不单调,可以 选择把s排序或者使用三分、set在凸包上面找了,但是这样无论如何时间复杂度都变成了O(
nlog2n
n
l
o
g
2
n
),就要比直接DPO(nlogn)慢一点啦。
回到这道题,出于懒惰不想打公式,去看其他人的题解吧(手动滑稽)
哈哈哈哈,放风筝的感觉哎QAQ
代码
#include<cmath>
#include<queue>
#include<cctype>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=4e5+105;
int n,L;
LL sum[maxn],s[maxn],x[maxn],y[maxn],f[maxn];
int q[maxn],front,rear;
void Init()
{
scanf("%d%d",&n,&L);
int c;
for(int i=1;i<=n;i++)
{
scanf("%d",&c);
sum[i]=sum[i-1]+c;
s[i]=s[i-1]+c+1;
}
}
#define calc(a,b) (double)(y[a]-y[b])/(x[a]-x[b])
void dp()
{
int j;
q[rear++]=0;
for(int i=1;i<=n;i++)
{
while(rear-front>1 && 2*(s[i]-(L+1))>=calc(q[front],q[front+1]) )front++;
j=q[front];
f[i]=f[j]+(i-j-1+sum[i]-sum[j]-L)*(i-j-1+sum[i]-sum[j]-L);
y[i]=s[i]*s[i]+f[i];x[i]=s[i];
while(rear-front>1 && calc(q[rear-1],i) <= calc(q[rear-1],q[rear-2]) )rear--;
q[rear++]=i;
}
printf("%lld\n",f[n]);
}
int main()
{
//freopen("in.txt","r",stdin);
Init();
dp();
return 0;
}