首先得讲一下单调队列,顾名思义,单调队列就是队列中的每个元素具有单调性,如果是单调递增队列,那么每个元素都是单调递增的,反正,亦然。

那么如何对单调队列进行操作呢?

是这样的:对于单调队列而言,队首和队尾都可以进行出队操作,但只有队尾能够进行入队操作。

至于如何来维护单调队列,这里以单调递增队列为例:

1、如果队列的长度是一定的,首先判断队首元素是否在规定范围内,如果不再,则队首指针向后移动。(至于如何来判断是否在制定范围内,一般而言,我们可以给每个元素设定一个入队的序号,这样就能够知道每个元素原来的顺序了)。

2、每次加入元素是,如果元素小于队尾元素且队列非空,则减小尾指针,队尾元素出队列,直到保持队列单调性为止。

题目链接:http://acm.fzu.edu.cn/problem.php?pid=1894

单调队列的入门题,我们给每个队列中的元素设定一个入队序号,并且设置一个变量来记录当前有多少人离开,这样我们可以维护一个单调递减队列,每次入队的时候,找当前元素适合的位置,每次出队列的时候,判断当前队首元素的入队序号与离开总入数的大小,如果小于等于,则说明当前队首元素应该已经在出队范围内,那么队首指针应该向后移动,直到找到元素的序号比当前离开的总人数大的那个元素,并且出队列。

 /*************************************************************************
> File Name: fzu1894.cpp
> Author: syhjh
> Created Time: 2014年03月11日 星期二 08时55分28秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int MAXN = ( + );
struct Node {
int val, num;
}; Node que[MAXN]; int main()
{
char s1[], s2[];
int Case;
scanf("%d", &Case);
while (Case--) {
int head = , tail = -, val;
int num = , level = ;
scanf("%s", s1);
while (~scanf("%s", s1)) {
if (strcmp(s1, "END") == ) {
break;
}
if (s1[] == 'C') {
scanf("%s %d", s2, &val);
//找到当前值适合插入的位置,并且将其后面的元素全部舍弃
while (head <= tail && que[tail].val <= val) tail--;
que[++tail].val = val;
que[tail].num = ++num;
} else if (s1[] == 'Q') {
//level记录了有多少个离开,因此我们要找的是队头元素进队列时的序号大于
//目前离开的总人数,这样才能够说明当前元素还在队列中
while (head <= tail && que[head].num <= level) {
head++;
}
if (tail < head) {
puts("-1");
} else
printf("%d\n", que[head].val);
} else
level++;
}
}
return ;
}

题目链接:http://poj.org/problem?id=2823

比较裸的单调队列,可以开两个队列来保存结果,一个单调递增来保存最小值,一个单调递减来保存最大值,每个元素入队列时都给一个入队编号,然后我们在判断的时候,只要判断当前元素的序号与队首元素的序号相差不大与K,则最值就是当前队首元素,否则,队首指针向后移动,直到找到一个符合条件的元素。

 /*************************************************************************
> File Name: poj2823.cpp
> Author: syhjh
> Created Time: 2014年03月11日 星期二 09时45分04秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std; const int MAXN = ( + );
struct Node {
int val, index;
}; Node que1[MAXN], que2[MAXN];
int N, K, M;
int num[MAXN];
int ans1[MAXN], ans2[MAXN]; void getSolve1()
{
int head = , tail = -, len = K;
M = ;
for (int i = ; i <= N; i++) {
while (head <= tail && num[i] <= que1[tail].val) {
tail--;
}
que1[++tail].val = num[i];
que1[tail].index = i;
if (i - len == ) {
while (head <= tail && i - que1[head].index + > K) {
head++;
}
ans1[++M] = que1[head].val;
len++;
}
}
} void getSolve2()
{
int head = , tail = -, len = K;
M = ;
for (int i = ; i <= N; i++) {
while (head <= tail && num[i] >= que2[tail].val) {
tail--;
}
que2[++tail].val = num[i];
que2[tail].index = i;
if (i - len == ) {
while (head <= tail && i - que2[head].index + > K) {
head++;
}
ans2[++M] = que2[head].val;
len++;
}
}
} int main()
{
while (~scanf("%d %d", &N, &K)) {
for (int i = ; i <= N; i++) {
scanf("%d", &num[i]);
}
getSolve1();
getSolve2();
for (int i = ; i <= M; i++) {
if (i == M) printf("%d\n", ans1[i]);
else printf("%d ", ans1[i]);
}
for (int i = ; i <= M; i++) {
if (i == M) printf("%d\n", ans2[i]);
else printf("%d ", ans2[i]);
}
}
return ;
}

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3415

题目的意思就是让你求最大的长度不超过K的连续序列的和。

思路:由于序列的环状特点,可以在最后添加K-1个数,并且用sum[i]表示1到i的连续和,于是sum[j] - sum[i - 1]就是i到j的连续和了。

那么对于每一个sum[j],用sum[j]来减去最小的sum[i](满足j - i >= K - 1),这样的话,就可以用单调队列来维护最小sum[i]下标了。

 /*************************************************************************
> File Name: hdu3415.cpp
> Author: syhjh
> Created Time: 2014年03月11日 星期二 10时43分42秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <climits>
#include <algorithm>
#include <deque>
using namespace std; const int MAXN = ( + );
int N, K;
int sum[MAXN], num[MAXN];
int que[MAXN]; int main()
{
int Case;
scanf("%d", &Case);
while (Case--) {
scanf("%d %d", &N, &K);
sum[] = ;
for (int i = ; i <= N; i++) {
scanf("%d", &num[i]);
sum[i] = sum[i - ] + num[i];
}
for (int i = N + ; i <= N + K - ; i++) {
sum[i] = sum[i - ] + num[i - N];
}
int head = , tail = -;
deque<int > deq;
int st, ed, ans = INT_MIN;
for (int i = ; i <= N + K - ; i++) {
while (head <= tail && sum[i - ] < sum[que[tail]]) {
tail--;
}
while (head <= tail && i - que[head] > K) {
head++;
}
que[++tail] = i - ;
if (sum[i] - sum[que[head]] > ans) {
ans = sum[i] - sum[que[head]];
st = que[head] + ;
ed = i;
}
/*
while (!deq.empty() && sum[i - 1] < sum[deq.back()]) {
deq.pop_back();
}
while (!deq.empty() && i - deq.front() > K) {
deq.pop_front();
}
deq.push_back(i - 1);
if (sum[i] - sum[deq.front()] > ans) {
ans = sum[i] - sum[deq.front()];
st = deq.front() + 1;
ed = i;
}*/
}
if (ed > N) ed -= N;
printf("%d %d %d\n", ans, st, ed);
}
return ;
}

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3507

这是我的第一道斜率优化的题目,整整看了一个下午和一个晚上的时间才有点明白过来。

下面的这位大牛写的很好:http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html

我自己的代码中也已有详细的注释,纯粹是对这题的理解!

 /*************************************************************************
> File Name: hdu3507.cpp
> Author: syhjh
> Created Time: 2014年03月11日 星期二 21时13分52秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std; const int MAXN = ( + );
int N, M;
int num[MAXN], sum[MAXN];
int dp[MAXN];
int que[MAXN]; 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 * sum[j] - * sum[k];
} int getDp(int i, int j)
{
return dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + M;
} int main()
{
while (~scanf("%d %d", &N, &M)) {
sum[] = ;
for (int i = ; i <= N; i++) {
scanf("%d", &num[i]);
sum[i] = sum[i - ] + num[i];
}
int head = , tail = ;
for (int i = ; i <= N; i++) {
//这里我假设,当k < j < i时,如果j比k优的话,有:
//dp[j] + (sum[i] - sum[j]) ^ 2 + M <= dp[k] + (sum[i] - sum[k]) ^ 2 + M;
//化简即有:(dp[j]+ sum[j] ^ 2) - (d[k] + sum[k] ^ 2) <= sum[i] * 2(sum[j] - sum[k])
//令yj = dp[j] + sum[j] ^ 2, yk = dp[k] + sum[k] ^ 2;
//xj = 2 * sum[j], xk = 2 * sum[k];
//于是有(yj - yk)/(xj - xk) <= sum[i]; 这里简记为g[j, k] <= sum[i];
//由于我这里假设k < j < i时,j比k优,说明如果满足上面的不等式,k是取不到的
//于是就可以把k(概括的讲是j前面的数字剔除掉,于是有了下面head指针的移动
while (head < tail && getUp(que[head + ], que[head])
<= sum[i] * getDown(que[head + ], que[head])) {
head++;
}
//根据等式dp[i] = dp[j] + (sum[i] - sum[j]) ^ 2 + M;
//此时que[head]保留的就是最优值
//这样每次求得的dp[i]就都是最有的了
dp[i] = getDp(i, que[head]);
//上面假设k < j < i,当我加入新元素x时,有k < j < i < x,若有g[x, i] <= g[i, j];
//那么说明此时新加入的x点比原来的i点更优,于是应该替换原来的点i,于是就有了下面
//的tail指针左移的情况
while (head < tail && getUp(i, que[tail]) * getDown(que[tail], que[tail - ])
<= getUp(que[tail], que[tail - ]) * getDown(i, que[tail])) {
tail--;
}
que[++tail] = i;
}
printf("%d\n", dp[N]);
}
return ;
}

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3480

思路:状态方程很容易写,dp[i][j]表示前i个数,分成j组的最小值,于是可以得出方程:dp[i][j] = min(dp[k][j - 1] + (num[i] - num[k + 1) ^ 2) (其中1 <= k < i).可是这个方程的复杂度可是O(n * m * n)。。对于n <=10000, m <=5000这样的数据规模显然是吃不消的。。。

怎么办呢?

可以试试斜率优化:这里我们假设对于k1 < k2 < i.方程在k2处的取值由于在k1处的取值,于是有

dp[k2][j-1] + (num[i]- num[k2 + 1]) ^2 <=  dp[k1][j-1] + (num[i]- num[k1 + 1]) ^ 2;

两边移项化简可得:dp[k2][j-1] + num[k2+ 1] ^2 - (dp[k1][j-1] + num[k1+ 1] ^2) <= num[i] * (2 * num[k2 + 1] - 2 * num[k1 + 1]);

我们令

yk2 = dp[k2][j-1] + num[k2 + 1] ^ 2;

yk1 = dp[k1][j- 1] + num[k1 + 1] ^ 2;

xk2 = 2 * num[k2 + 1];

xk1 = 2 * num[k1 + 1];

于是有:(yk2 - yk1)/(xk2 - xk1) <= num[i].

这里我们简记为g[k2, k1] = (yk2 - yk1)/(xk2 - xk1);

由于我们一开始假设对于k1 < k2 < i,有k2比k1优,此时满足的条件是g[k2, k1] <= num[i],那么放过来说,当我们的方程满足g[k2, k1] <= num[i]时,k2比k1优,此时就可以去掉k1,也就是单调队列中的头指针向后移动。

假设对于k1 < k2 < k3,有g[k3, k2] <= g[k2, k1].由于我们之前假设当k2优于k1时有g[k2, k1] <= num[i],则g[k3, k2] <= g[k2,k1] <= num[i].于是就有k3 优于k2,又因为k2优于k1,说明k2是永远都取不到的,这样的话,我们可以直接把k2从队尾删除。然后我们重复上一步骤,直到g[k3, k2] > g[k2, k1].

 /*************************************************************************
> File Name: hdu3480.cpp
> Author: syhjh
> Created Time: 2014年03月12日 星期三 09时51分55秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int MAXN = ( + );
const int MAXM = ( + );
int N, M;
int num[MAXN];
int dp[MAXN][MAXM];
int que[MAXN]; //k1 < k2
//yk2 - yk1部分
int getUp(int k1, int k2, int j)
{
int yk2 = dp[k2][j - ] + num[k2 + ] * num[k2 + ];
int yk1 = dp[k1][j - ] + num[k1 + ] * num[k1 + ];
return yk2 - yk1;
} //k1 < k2
//xk2 - xk1部分
int getDown(int k1, int k2)
{
int xk2 = * num[k2 + ];
int xk1 = * num[k1 + ];
return xk2 - xk1;
} //dp[i][j] = dp[k][j - 1] + (num[i] - num[k + 1]) ^ 2;
int getDp(int i, int k, int j)
{
return dp[k][j - ] + (num[i] - num[k + ]) * (num[i] - num[k + ]);
} int main()
{
int Case, t = ;
scanf("%d", &Case);
while (Case--) {
scanf("%d %d", &N, &M);
for (int i = ; i <= N; i++) {
scanf("%d", &num[i]);
}
sort(num + , num + + N);
for (int i = ; i <= N; i++) {
dp[i][] = (num[i] - num[]) * (num[i] - num[]);
}
que[] = ;
for (int j = ; j <= M; j++) {
int head = , tail = ;
for (int i = j; i <= N; i++) {
while (head < tail && getUp(que[tail], i, j) * getDown(que[tail - ], que[tail]) <= getUp(que[tail - ], que[tail], j) * getDown(que[tail], i)) {
tail--;
}
que[++tail] = i;
while (head < tail && getUp(que[head], que[head + ], j)
<= num[i] * getDown(que[head], que[head + ])) {
head++;
}
dp[i][j] = getDp(i, que[head], j);
}
}
printf("Case %d: %d\n", t++, dp[N][M]);
}
return ;
}

题目链接:http://poj.org/problem?id=3709

思路:状态方程很容易想,dp[i]表示处理到i为止的最小值,于是有dp[i] = min(dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);

对于 n <= 500000的数据规模,O(n^2)的算法必然要T。

这里可以用斜率优化.

假设对于k1 < k2 < i有k2处的值优于k1处的值,于是有dp[k2] + (sum[i] - sum[k2] - (i - k2) * (num[k2 + 1]) <= dp[k1] + (sum[i] - sum[k1] - (i - k1)* (num[k1 + 1]));

化简后可得:(dp[k2] - sum[k2] + k2 * num[k2 + 1]) - (dp[k1] - sum[k1] + k1 * num[k1 + 1]) <= i * (num[k2 + 1] - num[k1 + 1]);

令yk2 = dp[k2] - sum[k2] + k2 * num[k2 + 1];

yk1 = dp[k1] - sum[k1] + k1 * num[k1 + 1];

xk2 = num[k2 + 1];

xk1 = num[k1 + 1];

于是有(yk2 - yk1) <= i * (xk2 - xk1);

由于我们一开始假设k1 < k2 < i,有k2优于k1,也就是说如果满足上述方程:g[k2, k1] = (yk2 - yk1)/ (xk2 - xk1) <= i成立,那么k2就比k1优,也就是说k1是取不到的,由此队首指针要向后移动。

若k1 < k2 < k3 ,如果有g[k3, k2 ] <= g[k2, k1] 由于g[k2, k1] <= i, 那么g[k3, k2] <= i,也就是说k3比k2优,又k2比k1优,于是k2是取不到的,那么k2可以从队尾删除。

 /*************************************************************************
> File Name: poj3709.cpp
> Author: syhjh
> Created Time: 2014年03月12日 星期三 16时32分07秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int MAXN = ( + );
typedef long long ll;
int N, K;
ll num[MAXN], sum[MAXN];
ll dp[MAXN];
ll que[MAXN]; //yk2 - yk1 , k1 < k2 < i
ll getUp(int k1, int k2)
{
ll yk1 = dp[k1] - sum[k1] + k1 * num[k1 + ];
ll yk2 = dp[k2] - sum[k2] + k2 * num[k2 + ];
return yk2 - yk1;
} //xk2 - xk1
ll getDown(int k1, int k2)
{
return num[k2 + ] - num[k1 + ];
} //dp[i] = dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);
ll getDp(int i, int j)
{
return dp[j] + (sum[i] - sum[j] - (i - j) * num[j + ]);
} int main()
{
int Case;
scanf("%d", &Case);
while (Case--) {
scanf("%d %d", &N, &K);
sum[] = ;
for (int i = ; i <= N; i++) {
scanf("%lld", &num[i]);
sum[i] = sum[i - ] + num[i];
}
int head = , tail = ;
for (int i = ; i <= N; i++) {
while (head < tail && getUp(que[head], que[head + ])
<= i * getDown(que[head], que[head + ])) {
head++;
}
dp[i] = getDp(i, que[head]);
//由于我们要加入的数是i - (k - 1),但是要保证前一组的数至少有k个相同
if (i - (K - ) >= K) {
int x = i - (K - );
while (head < tail && getUp(que[tail], x) * getDown(que[tail - ], que[tail]) <= getUp(que[tail - ], que[tail]) * getDown(que[tail], x)) {
tail--;
}
que[++tail] = x;
}
}
printf("%lld\n", dp[N]);
}
return ;
}

题目链接:http://poj.org/problem?id=1180

状态方程比较难想。

dp[i] 表示第i个任务到n的最小花费,于是有dp[i] = min{dp[j] + (S + sumT[i] - sumT[j]) * (sumF[i] - sumF[j]) + (S + sumT[i] - sumT[j]) * sumF[j]} ;

化简后即得:dp[i] = min{dp[j] + (S + sumT[i] - sumT[j]) * sumF[i];

于是令k1 < k2 有k1 优于k2....步骤基本上就是一样的了。

 /*************************************************************************
> File Name: poj1180.cpp
> Author: syhjh
> Created Time: 2014年03月12日 星期三 21时26分48秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int MAXN = ( + );
int N, S;
int t[MAXN], f[MAXN];
int sumT[MAXN], sumF[MAXN];
int dp[MAXN];
int que[MAXN]; //yk2 - yk1, k1 < k2;
int getUp(int k1, int k2)
{
return dp[k1] - dp[k2];
} //xk2 - xk1;
int getDown(int k1, int k2)
{
return sumT[k1] - sumT[k2];
} int getDp(int i, int j)
{
return dp[j] + (S + sumT[i] - sumT[j]) * sumF[i];
} int main()
{
while (~scanf("%d %d", &N, &S)) {
dp[N + ] = sumT[N + ] = sumF[N + ] = ;
for (int i = ; i <= N; i++) {
scanf("%d %d", &t[i], &f[i]);
}
for (int i = N; i >= ; i--) {
sumT[i] = sumT[i + ] + t[i];
sumF[i] = sumF[i + ] + f[i];
}
int head = , tail = -;
que[++tail] = N + ;
for (int i = N; i >= ; i--) {
while (head < tail && getUp(que[head + ], que[head])
<= sumF[i] * getDown(que[head + ], que[head])) {
head++;
}
dp[i] = getDp(i, que[head]);
while (head < tail && getUp(i, que[tail]) * getDown(que[tail], que[tail - ])
<= getUp(que[tail], que[tail - ]) * getDown(i, que[tail])) {
tail--;
}
que[++tail] = i;
}
printf("%d\n", dp[]);
}
return ;
}

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2993

思路:ans[i] = min{(sum[i] - sum[j]) / (i - j)), 我们把(i, sum[i])看成一个点,那么不就是求斜率的最大值吗?由于数据规模为10万级别,O(N^2)的算法必然要T。

于是可以用单调队列来优化!

 /*************************************************************************
> File Name: hdu2993.cpp
> Author: syhjh
> Created Time: 2014年03月12日 星期三 22时26分13秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std; const int MAXN = ( + );
template < class T > inline T getMax(const T &a, const T &b)
{
return a > b ? a : b;
} int N, K, num[MAXN];
double sum[MAXN];
double ans;
int que[MAXN]; int main()
{
while (~scanf("%d %d", &N, &K)) {
sum[] = ;
for (int i = ; i <= N; i++) {
scanf("%d", &num[i]);
sum[i] = sum[i - ] + num[i] * 1.0;
}
int head = , tail = -;
que[++tail] = ;
ans = 0.0;
for (int i = K; i <= N; i++) {
int index = i - K;
while (head < tail) {
double y1 = sum[que[tail]] - sum[que[tail - ]];
double x1 = que[tail] - que[tail - ];
double y2 = sum[index] - sum[que[tail]];
double x2 = index - que[tail];
if (y1 * x2 >= y2 * x1) tail--;
else break;
}
que[++tail] = index;
while (head < tail) {
double y1 = sum[que[head]] - sum[i];
double x1 = que[head] - i;
double y2 = sum[que[head + ]] - sum[i];
double x2 = que[head + ] - i;
if (y1 * x2 <= y2 * x1) head++;
else break;
}
ans = getMax(ans, (sum[i] - sum[que[head]]) / (i - que[head]) * 1.0);
}
printf("%.2lf\n", ans);
}
return ;
}

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2829

思路:dp[i][j]表示前j个数组成i组的最小值,w[i]表示1-i的价值,sum[i]表示1-i的和。于是我们可以得出递推方程:dp[i][j] = min{dp[i-1][k] + w[j] - w[k] - sum[k] *  (sum[j] - sum[k])} (i<= k < j);

毫无疑问,如果按照一般的解法,那么复杂度将是O(n^3),对于n<= 1000的规模显吃不消,那怎么办呢,试试斜率优化!

我们设k1 < k2 < i时,k2优于k1,于是可以得到:dp[i-1][k2] + w[j] - w[k2] - sum[k2] * (sum[j] - sum[k2]) <= dp[i-1][k1] + w[j] - w[k1] - sum[k1] * (sum[j] - sum[k1]);

化简后可得:dp[i-1][k2] - w[k2] + sum[k2] * sum[k2] - (dp[i-1][k1] - w[k1] + sum[k1] * sum[k1]) <= sum[j] * (sum[k2- sum[k1]) ,令

yk1 = dp[i-1][k1] - w[k1] + sum[k1] * sum[k1];

yk2 = dp[i-1][k2] - w[k2] + sum[k2] * sum[k2];

xk1 = sum[k1];

xk2 = sum[k2];

于是有(yk2- yk1)<= sum[j] * (xk2- xk1).由于我们一开始是假设当k1 < k2 < i时,k2处的取值优于k1,于是我们可以得出当满足(yk2 - yk1) <= sum[i] * (xk2 -xk1)(这里我为了方便起见,简记为g[k2, k1] = (yk2 - yk1)/ (xk2 - xk1))时,k2比k1优,那么也就是说k1是取不到的,于是这是我们应该移动队首指针,将k1从队首删除。

设k1 < k2 < k3< i,如果我们有g[k3, k2] <= g[k2, k1],由于g[k2, k1] <= sum[j],于是g[k3,k2] <= sum[j],那么也就是说k3处由于k2处,又k2优于k1,说明k2是取不到的,于是k2也可以从队尾删除。

这里说一下单调队列的作用,从递推关系式可以看出,我们是要找当前最优的K值,那么que这个单调队列的队首保存的就是当前最有的K值。

 /*************************************************************************
> File Name: hdu2829.cpp
> Author: syhjh
> Created Time: 2014年03月13日 星期四 19时50分07秒
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int MAXN = ( + );
int N, M;
int num[MAXN], sum[MAXN], w[MAXN]; //w[i]表示1-i算一组的val
int dp[MAXN][MAXN]; //dp[i][j]表示前j个数分成i组的最小val
int que[MAXN]; //yk2 - yk1, k1 < k2;
int getUp(int k1, int k2, int i)
{
int yk1 = dp[i - ][k1] - w[k1] + sum[k1] * sum[k1];
int yk2 = dp[i - ][k2] - w[k2] + sum[k2] * sum[k2];
return yk2 - yk1;
} //xk2 - xk1
int getDown(int k1, int k2)
{
return sum[k2] - sum[k1];
} //dp[i][j] = dp[i - 1][k] + (w[j] - w[k] - sum[k] * (sum[j] - sum[k]));
int getDp(int i, int j, int k)
{
return dp[i - ][k] + (w[j] - w[k] - sum[k] * (sum[j] - sum[k]));
} int main()
{
while (~scanf("%d %d", &N, &M)) {
if (N == && M == ) break;
sum[] = w[] = ;
for (int i = ; i <= N; i++) {
scanf("%d", &num[i]);
sum[i] = sum[i - ] + num[i];
w[i] = w[i - ] + sum[i - ] * num[i];
}
for (int i = ; i <= N; i++) {
dp[][i] = w[i];
}
for (int i = ; i <= M + ; i++) {
int head = , tail = -;
que[++tail] = i - ;
for (int j = i; j <= N; j++) {
while (head < tail && getUp(que[head], que[head + ], i)
<= sum[j] * getDown(que[head], que[head + ])) {
head++;
}
dp[i][j] = getDp(i, j, que[head]);
while (head < tail && getUp(que[tail], j, i) * getDown(que[tail - ], que[tail]) <= getUp(que[tail - ], que[tail], i) * getDown(que[tail], j)) {
tail--;
}
que[++tail] = j;
}
}
printf("%d\n", dp[M + ][N]);
}
return ;
}

PS:单调队列做多了,就能发现只要推出递推方程,然后转化为斜率,那么剩下的基本上就是模板题了!

单调队列 && 斜率优化dp 专题的更多相关文章

  1. HDU 3507 单调队列 斜率优化

    斜率优化的模板题 给出n个数以及M,你可以将这些数划分成几个区间,每个区间的值是里面数的和的平方+M,问所有区间值总和最小是多少. 如果不考虑平方,那么我们显然可以使用队列维护单调性,优化DP的线性方 ...

  2. 洛谷P1725 琪露诺 (单调队列/堆优化DP)

    显然的DP题..... 对于位置i,它由i-r~i-l的位置转移过来,容易得到方程 dp[i]=dp[i]+max(dp[i−r],...,dp[i−l]). 第一种:n2的暴力,只能拿部分分. 1 ...

  3. DP单调队列--斜率优化P3195

    题意:https://www.luogu.com.cn/problem/P3195 思路:https://www.luogu.com.cn/problemnew/solution/P3195 #def ...

  4. 土地购买 (斜率优化dp)

    土地购买 (斜率优化dp) 题目描述 农夫 \(John\) 准备扩大他的农场,他正在考虑$ N(1 \leqslant N \leqslant 50,000)$ 块长方形的土地. 每块土地的长宽满足 ...

  5. 【Luogu】P3195玩具装箱(斜率优化DP)

    这题还是比较炫的 题目链接 我们设f[i]是已经装了前i个玩具,且第i个玩具是某箱子里装的最后一个东西(废话) 那我们很轻松可以想到一个转移方程 ;i<=n;++i) ;j<i;++j) ...

  6. 动态规划专题(五)——斜率优化DP

    前言 斜率优化\(DP\)是难倒我很久的一个算法,我花了很长时间都难以理解.后来,经过无数次的研究加以对一些例题的理解,总算啃下了这根硬骨头. 基本式子 斜率优化\(DP\)的式子略有些复杂,大致可以 ...

  7. 决策单调性优化dp 专题练习

    决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队 ...

  8. 斜率优化dp(POJ1180 Uva1451)

    学这个斜率优化dp却找到这个真心容易出错的题目,其中要从n倒过来到1的确实没有想到,另外斜率优化dp的算法一开始看网上各种大牛博客自以为懂了,最后才发现是错了. 不过觉得看那些博客中都是用文字来描述, ...

  9. 斜率优化dp

    转载自http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html 我们知道,有些DP方程可以转化成DP[i]=f[j]+x[i]的形 ...

随机推荐

  1. Java 静态内部类与非静态内部类 学习记录.

    目的 为什么会有这篇文章呢,是因为我在学习各种框架的时候发现很多框架都用到了这些内部类的小技巧,虽然我平时写代码的时候基本不用,但是看别人代码的话至少要了解基本知识吧,另外到底内部类应该应用在哪些场合 ...

  2. js删除数组指定元素

    删除js数组中制定的元素,这里用到了jquery. var a = new Array("a","b","cc","d3" ...

  3. SQL Server 2008在Windows 10上不支持

    https://support.microsoft.com/en-us/kb/2681562 For SQL Server 2008 R2 SQL Server 2008 R2 is not supp ...

  4. QuartusII Design partion and logic lock

    Design partion Design partion常用于“增益变量(QIC)”,通过Design Partition对子模块进行“逻辑分区”,在Design Partition Window中 ...

  5. gitlab基本维护和使用

    基本介绍 GitLab是一个自托管的Git项目仓库,可以自己搭建个人代码管理的仓库,功能与github类似. 安装 下载 gitlab下载地址: https://about.gitlab.com/do ...

  6. Jquery制作--循环滚动列表

    自己模仿JQ插件的写法写了一个循环滚动列表插件,支持自定义上.下.左.右四个方向,支持平滑滚动或者间断滚动两种方式,都是通过参数设置.JQ里面有些重复的地方,暂时没想到更好的方法去精简.不过效果还是可 ...

  7. js中bind,call,apply方法的应用

    最近用js的类写东西,发现一个无比蛋疼的事,那就是封装的类方法中的this指针经常会改变指向,失去上下文,导致程序错误或崩溃. 比如: function Obj(){ this.type = &quo ...

  8. BZOJ 题目整理

    bzoj 500题纪念 总结一发题目吧,挑几道题整理一下,(方便拖板子) 1039:每条线段与前一条线段之间的长度的比例和夹角不会因平移.旋转.放缩而改变,所以将每条轨迹改为比例和夹角的序列,复制一份 ...

  9. 在Qt Creator 和在 vs2012 里添加信号和槽

    原文地址:http://www.cnblogs.com/li-peng/p/3644812.html 作者:李鹏 出处:http://www.cnblogs.com/li-peng/ 本文版权归作者和 ...

  10. zabbix自定义key

    zabbix自定义key 1.修改客户端配置文件 #vi /opt/zabbix/etc/zabbix_agentd.conf Include=/opt/zabbix/etc/zabbix_agent ...