【题目链接】 点击打开链接
【题意】大概题意就是要输出N个数字a[N],输出的时候可以连续的输出,每连续输出一串,它的费用是 “这串数字和的平方加上一个常数M”。
【写在前面】 思想参考BLOG : 点击打开链接
【解题方法】斜率优化, 做这个题之前还不知道什么叫斜率优化,或许是有了单调队列的理解基础,一看就懂了。现在把我的理解和推导写下来,供大家交流。
首先, 比较容易想到一个很朴素的方程 : 我们设 DP[i]表示输出到i时的 最小花费, 那么 sum[i] = 1到i的数字之和, 那么我们可以容易的列出来一个式子,
DP[i] = DP[j] + M + (sum[i] - sum[j]) ^ 2.然后显然这是一个二维的DP。题目数据为500000, 显然是不能通过此题的。那么考虑如何优化呢?
我们还是从上面的式子着手,我们我们假设k<j<i。如果在j的时候决策要比在k的时候决策好,那么也是就是dp[j]+M+(sum[i]-sum[j])^2<dp[k]+M+(sum[i]-sum[k])^2。
(因为是最小花费嘛,所以优就是小于)。
然后我们化简并移项得到 : DP[j] + Sum[j] ^ 2 - (DP[k] + Sum[k] ^ 2) / (2 * (Sum[j] - Sum[k])) < Sum[i]。 我们观察一下左边的式子,是不是就是
(yj - yk) / (xj - xk) ,这个形式难道不就是斜率的形式吗? 前面我们假设了 在 j的决策比在k的决策要好, 那么就是满足 (yj - yk) / (xj - xk) < Sum[i]时,j比k决策好。
然后斜率优化最关键的部分来了:(先说下,下面的g[i, j]代表的是斜率)现在从左到右,还是设k<j<i,如果g[i,j]<g[j,k],那么j点便永远不可能成为最优解,可以直接将它踢出我们的最优解集。为什么呢?
我们假设g[i,j]<sum[i],那么就是说i点要比j点优,排除j点。如果g[i,j]>=sum[i],那么j点此时是比i点要更优,但是同时g[j,k]>g[i,j]>sum[i]。这说明还有k点会比j点更优,同样排除j点。
排除多余的点,这便是一种优化!即是点的优化,以前听说过一个点优化不知道是不是说的斜率优化。
那么有了这个性质之后最优解如何寻找呢? 还是设k<j<i。由于我们排除了g[i,j]<g[j,k]的情况,所以整个有效点集呈现一种上凸性质,即k j的斜率要大于j i的斜率。
也即是这个图:
这样,从左到右,斜率之间就是单调递减的了。当我们的最优解取得在j点的时候,那么k点不可能再取得比j点更优的解了,于是k点也可以排除。换句话说,j点之前的点全部
不可能再比j点更优了,可以全部从解集中排除。
我们代码实现怎么写呢?
1,用一个单调队列来维护解集。
2,假设队列中从头到尾已经有元素a b c。那么当d要入队的时候,我们维护队列的上凸性质,即如果g[d,c]<g[c,b],那么就将c点删除。直到找到g[d,x]>=g[x,y]为止,并将d点加入在该位置中。
3,求解时候,从队头开始,如果已有元素a b c,当i点要求解时,如果g[b,a]<sum[i],那么说明b点比a点更优,a点可以排除,于是a出队。最后dp[i]=getDp(q[head])。
这个题虽然采用了斜率优化,但是还是有区别的在于,sum[i]并不是单调递增的,也有可能两个点的 x坐标是相同的,这就是为什么下面的斜率优化会用 <= 和 >= 的原因,
这个=一定不能缺,缺了钦定WA。
//
//Created by BLUEBUFF 2016/1/8
//Copyright (c) 2016 BLUEBUFF.All Rights Reserved
//
#pragma comment(linker,"/STACK:102400000,102400000")
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdio>
#include <time.h>
#include <cstdlib>
#include <cstring>
#include <sstream> //isstringstream
#include <iostream>
#include <algorithm>
using namespace std;
//using namespace __gnu_pbds;
typedef long long LL;
typedef pair<int, LL> pp;
#define REP1(i, a, b) for(int i = a; i < b; i++)
#define REP2(i, a, b) for(int i = a; i <= b; i++)
#define REP3(i, a, b) for(int i = a; i >= b; i--)
#define CLR(a, b) memset(a, b, sizeof(a))
#define MP(x, y) make_pair(x,y)
const int maxn = 500010;
const int maxm = 2e5;
const int maxs = 10;
const int maxp = 1e3 + 10;
const int INF = 1e9;
const int UNF = -1e9;
const int mod = 1e9 + 7;
int gcd(int x, int y) {return y == 0 ? x : gcd(y, x % y);}
//typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>order_set;
//head
//斜率优化
int n, m, head, tail, dp[maxn], q[maxn], sum[maxn], a[maxn];
int getDP(int i, int j){
return dp[j] + m + (sum[i] - sum[j]) * (sum[i] - sum[j]);
}
int getUP(int j, int k){
return dp[j] + sum[j] * sum[j] - (dp[k] + sum[k] * sum[k]);
}
int getDOWN(int j, int k){
return 2 * (sum[j] - sum[k]);
}
int main()
{
while(scanf("%d%d", &n, &m) != EOF)
{
REP2(i, 1, n) scanf("%d", &a[i]);
sum[0] = dp[0] = 0;
REP2(i, 1, n) sum[i] = sum[i - 1] + a[i];
head = tail = 0;
q[tail++] = 0;
REP2(i, 1, n){
while(head + 1 < tail && getUP(q[head + 1], q[head]) <= sum[i] * getDOWN(q[head + 1], q[head])) head++;
dp[i] = getDP(i, q[head]);
while(head + 1 < tail && getUP(i, q[tail - 1]) * getDOWN(q[tail - 1], q[tail - 2]) <= getUP(q[tail - 1], q[tail - 2]) * getDOWN(i, q[tail - 1])) tail--;
q[tail++] = i;
}
cout << dp[n] << endl;
}
return 0;
}