单调队列 && 斜率优化dp 专题
首先得讲一下单调队列,顾名思义,单调队列就是队列中的每个元素具有单调性,如果是单调递增队列,那么每个元素都是单调递增的,反正,亦然。
那么如何对单调队列进行操作呢?
是这样的:对于单调队列而言,队首和队尾都可以进行出队操作,但只有队尾能够进行入队操作。
至于如何来维护单调队列,这里以单调递增队列为例:
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 专题的更多相关文章
- HDU 3507 单调队列 斜率优化
斜率优化的模板题 给出n个数以及M,你可以将这些数划分成几个区间,每个区间的值是里面数的和的平方+M,问所有区间值总和最小是多少. 如果不考虑平方,那么我们显然可以使用队列维护单调性,优化DP的线性方 ...
- 洛谷P1725 琪露诺 (单调队列/堆优化DP)
显然的DP题..... 对于位置i,它由i-r~i-l的位置转移过来,容易得到方程 dp[i]=dp[i]+max(dp[i−r],...,dp[i−l]). 第一种:n2的暴力,只能拿部分分. 1 ...
- DP单调队列--斜率优化P3195
题意:https://www.luogu.com.cn/problem/P3195 思路:https://www.luogu.com.cn/problemnew/solution/P3195 #def ...
- 土地购买 (斜率优化dp)
土地购买 (斜率优化dp) 题目描述 农夫 \(John\) 准备扩大他的农场,他正在考虑$ N(1 \leqslant N \leqslant 50,000)$ 块长方形的土地. 每块土地的长宽满足 ...
- 【Luogu】P3195玩具装箱(斜率优化DP)
这题还是比较炫的 题目链接 我们设f[i]是已经装了前i个玩具,且第i个玩具是某箱子里装的最后一个东西(废话) 那我们很轻松可以想到一个转移方程 ;i<=n;++i) ;j<i;++j) ...
- 动态规划专题(五)——斜率优化DP
前言 斜率优化\(DP\)是难倒我很久的一个算法,我花了很长时间都难以理解.后来,经过无数次的研究加以对一些例题的理解,总算啃下了这根硬骨头. 基本式子 斜率优化\(DP\)的式子略有些复杂,大致可以 ...
- 决策单调性优化dp 专题练习
决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队 ...
- 斜率优化dp(POJ1180 Uva1451)
学这个斜率优化dp却找到这个真心容易出错的题目,其中要从n倒过来到1的确实没有想到,另外斜率优化dp的算法一开始看网上各种大牛博客自以为懂了,最后才发现是错了. 不过觉得看那些博客中都是用文字来描述, ...
- 斜率优化dp
转载自http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html 我们知道,有些DP方程可以转化成DP[i]=f[j]+x[i]的形 ...
随机推荐
- JS 获取CSS属性值
obj: 元素对象 attribute: 属性 返回值:该对象这个属性的值 function getDefaultStyle(obj,attribute){ // 返回最终样式函数,兼容IE和DOM, ...
- 1.2输出100以内的素数&输出前100个素数。
输出100以内的素数只是一个嵌套,在1.1的基础上添加一层循环,只需要注意从2开始,并且变量需要换一个. #include<stdio.h> int main() { ; ; i < ...
- c# 面向方面编程
AOP面向切面编程(Aspect Oriented Programming),是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.Spring框架用的核心技术就是AOP,是函数式编程的一 ...
- PHP获取接口数据(模拟Get)
当我们在做PHP开发的时候,很多时候需要对接口进行测试,或者更方便的调用一些已有模块的接口,取到结果并进行后续操作,我们可以通过curl进行模拟提交post和get请求,来去实现这些功能. 之后就可以 ...
- 兼容好的JS图片上传预览代码
转 : http://www.codefans.net/articles/1395.shtml 兼容好的JS图片上传预览代码 (谷歌,IE11) <html xmlns="http:/ ...
- 现代软件工程作业 github使用
Github使用 版本库的创建与同步 第一步:创建远程版本库并同步到本地 创建远程版本库 在地址栏输入www.github.com 并sign in 进入到个人主页,如下图示: 创建远程版本库:点击N ...
- STM32f103 定时器之编码器接口模式
背景 买了个Arduino的旋转编码器模块,配合STM32定时器的编码器模式实现了旋转角度以及圈数的计数.这种旋转编码器我能想到的实际应用场景暂时只有实体音量旋钮,鼠标的滚轮等,所以只实现了计数.阅读 ...
- 使用SVG绘制湖南地图
项目中有需求使用SVG绘制湖南地图,现把自己制作过程写一下供大家参考. 1.首先准备一张湖南地图(仅有各市边界线即可).(图片最好是PNG的,除了地图其它什么也没有) 2.准备SVG编辑工具SVGDe ...
- 新型的Hbb项目目录结构
- Hbb - ComponentPacket (底层组件包) 数据库组件 网络组件 格式化组件 - Resources (存放所有图片资源文件) - ToolClass (工具类/Helper 独立 ...
- C语言 活动安排问题之二
有若干个活动,第i个开始时间和结束时间是[Si,fi),活动之间不能交叠,要把活动都安排完,至少需要几个教室? #include <stdio.h> #include <string ...