单调队列,斜率优化dp 专题
来源:互联网 发布:dj香烟淘宝链接 编辑:程序博客网 时间:2024/06/06 20:42
首先得讲一下单调队列,顾名思义,单调队列就是队列中的每个元素具有单调性,如果是单调递增队列,那么每个元素都是单调递增的,反正,亦然。
那么如何对单调队列进行操作呢?
是这样的:对于单调队列而言,队首和队尾都可以进行出队操作,但只有队尾能够进行入队操作。
至于如何来维护单调队列,这里以单调递增队列为例:
1、如果队列的长度是一定的,首先判断队首元素是否在规定范围内,如果不再,则队首指针向后移动。(至于如何来判断是否在制定范围内,一般而言,我们可以给每个元素设定一个入队的序号,这样就能够知道每个元素原来的顺序了)。
2、每次加入元素是,如果元素小于队尾元素且队列非空,则减小尾指针,队尾元素出队列,直到保持队列单调性为止。
题目链接:http://acm.fzu.edu.cn/problem.php?pid=1894
单调队列的入门题,我们给每个队列中的元素设定一个入队序号,并且设置一个变量来记录当前有多少人离开,这样我们可以维护一个单调递减队列,每次入队的时候,找当前元素适合的位置,每次出队列的时候,判断当前队首元素的入队序号与离开总入数的大小,如果小于等于,则说明当前队首元素应该已经在出队范围内,那么队首指针应该向后移动,直到找到元素的序号比当前离开的总人数大的那个元素,并且出队列。
1 /************************************************************************* 2 > File Name: fzu1894.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月11日 星期二 08时55分28秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 using namespace std;11 12 const int MAXN = (1000000 + 10);13 struct Node {14 int val, num;15 };16 17 Node que[MAXN];18 19 int main()20 {21 char s1[7], s2[7];22 int Case;23 scanf("%d", &Case);24 while (Case--) {25 int head = 0, tail = -1, val;26 int num = 0, level = 0;27 scanf("%s", s1);28 while (~scanf("%s", s1)) {29 if (strcmp(s1, "END") == 0) {30 break;31 }32 if (s1[0] == 'C') {33 scanf("%s %d", s2, &val);34 //找到当前值适合插入的位置,并且将其后面的元素全部舍弃35 while (head <= tail && que[tail].val <= val) tail--;36 que[++tail].val = val;37 que[tail].num = ++num;38 } else if (s1[0] == 'Q') {39 //level记录了有多少个离开,因此我们要找的是队头元素进队列时的序号大于40 //目前离开的总人数,这样才能够说明当前元素还在队列中41 while (head <= tail && que[head].num <= level) {42 head++;43 }44 if (tail < head) {45 puts("-1");46 } else 47 printf("%d\n", que[head].val);48 } else 49 level++;50 }51 }52 return 0;53 }
题目链接:http://poj.org/problem?id=2823
比较裸的单调队列,可以开两个队列来保存结果,一个单调递增来保存最小值,一个单调递减来保存最大值,每个元素入队列时都给一个入队编号,然后我们在判断的时候,只要判断当前元素的序号与队首元素的序号相差不大与K,则最值就是当前队首元素,否则,队首指针向后移动,直到找到一个符合条件的元素。
1 /************************************************************************* 2 > File Name: poj2823.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月11日 星期二 09时45分04秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 #include <vector>11 using namespace std;12 13 const int MAXN = (1000000 + 100);14 struct Node {15 int val, index;16 };17 18 Node que1[MAXN], que2[MAXN];19 int N, K, M;20 int num[MAXN];21 int ans1[MAXN], ans2[MAXN];22 23 void getSolve1()24 {25 int head = 0, tail = -1, len = K;26 M = 0;27 for (int i = 1; i <= N; i++) {28 while (head <= tail && num[i] <= que1[tail].val) {29 tail--;30 }31 que1[++tail].val = num[i];32 que1[tail].index = i;33 if (i - len == 0) {34 while (head <= tail && i - que1[head].index + 1 > K) {35 head++;36 }37 ans1[++M] = que1[head].val;38 len++;39 }40 }41 }42 43 void getSolve2()44 {45 int head = 0, tail = -1, len = K;46 M = 0;47 for (int i = 1; i <= N; i++) {48 while (head <= tail && num[i] >= que2[tail].val) {49 tail--;50 }51 que2[++tail].val = num[i];52 que2[tail].index = i;53 if (i - len == 0) {54 while (head <= tail && i - que2[head].index + 1 > K) {55 head++;56 }57 ans2[++M] = que2[head].val;58 len++;59 }60 }61 }62 63 int main()64 {65 while (~scanf("%d %d", &N, &K)) {66 for (int i = 1; i <= N; i++) {67 scanf("%d", &num[i]);68 }69 getSolve1();70 getSolve2();71 for (int i = 1; i <= M; i++) {72 if (i == M) printf("%d\n", ans1[i]);73 else printf("%d ", ans1[i]);74 }75 for (int i = 1; i <= M; i++) {76 if (i == M) printf("%d\n", ans2[i]);77 else printf("%d ", ans2[i]);78 }79 }80 return 0;81 }
题目链接: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]下标了。
1 /************************************************************************* 2 > File Name: hdu3415.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月11日 星期二 10时43分42秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <climits>10 #include <algorithm>11 #include <deque>12 using namespace std;13 14 const int MAXN = (200000 + 200);15 int N, K;16 int sum[MAXN], num[MAXN];17 int que[MAXN];18 19 int main()20 {21 int Case;22 scanf("%d", &Case);23 while (Case--) {24 scanf("%d %d", &N, &K);25 sum[0] = 0;26 for (int i = 1; i <= N; i++) {27 scanf("%d", &num[i]);28 sum[i] = sum[i - 1] + num[i];29 }30 for (int i = N + 1; i <= N + K - 1; i++) {31 sum[i] = sum[i - 1] + num[i - N];32 }33 int head = 0, tail = -1;34 deque<int > deq;35 int st, ed, ans = INT_MIN; 36 for (int i = 1; i <= N + K - 1; i++) {37 while (head <= tail && sum[i - 1] < sum[que[tail]]) {38 tail--;39 }40 while (head <= tail && i - que[head] > K) {41 head++;42 }43 que[++tail] = i - 1;44 if (sum[i] - sum[que[head]] > ans) {45 ans = sum[i] - sum[que[head]];46 st = que[head] + 1;47 ed = i;48 }49 /*50 while (!deq.empty() && sum[i - 1] < sum[deq.back()]) {51 deq.pop_back();52 }53 while (!deq.empty() && i - deq.front() > K) {54 deq.pop_front();55 }56 deq.push_back(i - 1);57 if (sum[i] - sum[deq.front()] > ans) {58 ans = sum[i] - sum[deq.front()];59 st = deq.front() + 1;60 ed = i;61 }*/62 }63 if (ed > N) ed -= N;64 printf("%d %d %d\n", ans, st, ed);65 }66 return 0;67 }
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3507
这是我的第一道斜率优化的题目,整整看了一个下午和一个晚上的时间才有点明白过来。
下面的这位大牛写的很好:http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html
我自己的代码中也已有详细的注释,纯粹是对这题的理解!
1 /************************************************************************* 2 > File Name: hdu3507.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月11日 星期二 21时13分52秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 #include <cmath>11 using namespace std;12 13 const int MAXN = (500000 + 500);14 int N, M;15 int num[MAXN], sum[MAXN];16 int dp[MAXN];17 int que[MAXN];18 19 int getUp(int j, int k)20 {21 return (dp[j] + sum[j] * sum[j]) - (dp[k] + sum[k] * sum[k]);22 }23 24 int getDown(int j, int k)25 {26 return 2 * sum[j] - 2 * sum[k];27 }28 29 int getDp(int i, int j)30 {31 return dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + M;32 }33 34 35 int main()36 {37 while (~scanf("%d %d", &N, &M)) {38 sum[0] = 0;39 for (int i = 1; i <= N; i++) {40 scanf("%d", &num[i]);41 sum[i] = sum[i - 1] + num[i];42 }43 int head = 0, tail = 0;44 for (int i = 1; i <= N; i++) {45 //这里我假设,当k < j < i时,如果j比k优的话,有:46 //dp[j] + (sum[i] - sum[j]) ^ 2 + M <= dp[k] + (sum[i] - sum[k]) ^ 2 + M;47 //化简即有:(dp[j]+ sum[j] ^ 2) - (d[k] + sum[k] ^ 2) <= sum[i] * 2(sum[j] - sum[k])48 //令yj = dp[j] + sum[j] ^ 2, yk = dp[k] + sum[k] ^ 2;49 //xj = 2 * sum[j], xk = 2 * sum[k];50 //于是有(yj - yk)/(xj - xk) <= sum[i]; 这里简记为g[j, k] <= sum[i];51 //由于我这里假设k < j < i时,j比k优,说明如果满足上面的不等式,k是取不到的52 //于是就可以把k(概括的讲是j前面的数字剔除掉,于是有了下面head指针的移动53 while (head < tail && getUp(que[head + 1], que[head])54 <= sum[i] * getDown(que[head + 1], que[head])) {55 head++;56 }57 //根据等式dp[i] = dp[j] + (sum[i] - sum[j]) ^ 2 + M;58 //此时que[head]保留的就是最优值59 //这样每次求得的dp[i]就都是最有的了60 dp[i] = getDp(i, que[head]);61 //上面假设k < j < i,当我加入新元素x时,有k < j < i < x,若有g[x, i] <= g[i, j];62 //那么说明此时新加入的x点比原来的i点更优,于是应该替换原来的点i,于是就有了下面63 //的tail指针左移的情况64 while (head < tail && getUp(i, que[tail]) * getDown(que[tail], que[tail - 1]) 65 <= getUp(que[tail], que[tail - 1]) * getDown(i, que[tail])) {66 tail--;67 }68 que[++tail] = i;69 }70 printf("%d\n", dp[N]);71 }72 return 0;73 }74 75 76
题目链接: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].
1 /************************************************************************* 2 > File Name: hdu3480.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月12日 星期三 09时51分55秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 using namespace std;11 12 const int MAXN = (10000 + 100);13 const int MAXM = (5000 + 50);14 int N, M;15 int num[MAXN];16 int dp[MAXN][MAXM];17 int que[MAXN];18 19 //k1 < k220 //yk2 - yk1部分21 int getUp(int k1, int k2, int j)22 {23 int yk2 = dp[k2][j - 1] + num[k2 + 1] * num[k2 + 1];24 int yk1 = dp[k1][j - 1] + num[k1 + 1] * num[k1 + 1];25 return yk2 - yk1;26 }27 28 //k1 < k229 //xk2 - xk1部分30 int getDown(int k1, int k2)31 {32 int xk2 = 2 * num[k2 + 1];33 int xk1 = 2 * num[k1 + 1];34 return xk2 - xk1;35 }36 37 //dp[i][j] = dp[k][j - 1] + (num[i] - num[k + 1]) ^ 2;38 int getDp(int i, int k, int j)39 {40 return dp[k][j - 1] + (num[i] - num[k + 1]) * (num[i] - num[k + 1]);41 }42 43 44 int main()45 {46 int Case, t = 1;47 scanf("%d", &Case);48 while (Case--) {49 scanf("%d %d", &N, &M);50 for (int i = 1; i <= N; i++) {51 scanf("%d", &num[i]);52 }53 sort(num + 1, num + 1 + N);54 for (int i = 1; i <= N; i++) {55 dp[i][1] = (num[i] - num[1]) * (num[i] - num[1]);56 }57 que[0] = 1;58 for (int j = 2; j <= M; j++) {59 int head = 0, tail = 0;60 for (int i = j; i <= N; i++) {61 while (head < tail && getUp(que[tail], i, j) * getDown(que[tail - 1], que[tail]) <= getUp(que[tail - 1], que[tail], j) * getDown(que[tail], i)) {62 tail--;63 }64 que[++tail] = i;65 while (head < tail && getUp(que[head], que[head + 1], j)66 <= num[i] * getDown(que[head], que[head + 1])) {67 head++;68 }69 dp[i][j] = getDp(i, que[head], j);70 }71 }72 printf("Case %d: %d\n", t++, dp[N][M]);73 }74 return 0;75 }76 77 78 79
题目链接: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可以从队尾删除。
1 /************************************************************************* 2 > File Name: poj3709.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月12日 星期三 16时32分07秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 using namespace std;11 12 const int MAXN = (500000 + 50);13 typedef long long ll;14 int N, K;15 ll num[MAXN], sum[MAXN];16 ll dp[MAXN];17 ll que[MAXN];18 19 //yk2 - yk1 , k1 < k2 < i20 ll getUp(int k1, int k2)21 {22 ll yk1 = dp[k1] - sum[k1] + k1 * num[k1 + 1];23 ll yk2 = dp[k2] - sum[k2] + k2 * num[k2 + 1];24 return yk2 - yk1;25 }26 27 //xk2 - xk1 28 ll getDown(int k1, int k2)29 {30 return num[k2 + 1] - num[k1 + 1];31 }32 33 //dp[i] = dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);34 ll getDp(int i, int j)35 {36 return dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);37 }38 39 40 int main()41 {42 int Case;43 scanf("%d", &Case);44 while (Case--) {45 scanf("%d %d", &N, &K);46 sum[0] = 0;47 for (int i = 1; i <= N; i++) {48 scanf("%lld", &num[i]);49 sum[i] = sum[i - 1] + num[i];50 }51 int head = 0, tail = 0;52 for (int i = 1; i <= N; i++) {53 while (head < tail && getUp(que[head], que[head + 1])54 <= i * getDown(que[head], que[head + 1])) {55 head++;56 }57 dp[i] = getDp(i, que[head]);58 //由于我们要加入的数是i - (k - 1),但是要保证前一组的数至少有k个相同59 if (i - (K - 1) >= K) {60 int x = i - (K - 1);61 while (head < tail && getUp(que[tail], x) * getDown(que[tail - 1], que[tail]) <= getUp(que[tail - 1], que[tail]) * getDown(que[tail], x)) {62 tail--;63 }64 que[++tail] = x;65 }66 }67 printf("%lld\n", dp[N]);68 }69 return 0;70 }
题目链接: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....步骤基本上就是一样的了。
1 /************************************************************************* 2 > File Name: poj1180.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月12日 星期三 21时26分48秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 using namespace std;11 12 const int MAXN = (10000 + 100);13 int N, S;14 int t[MAXN], f[MAXN];15 int sumT[MAXN], sumF[MAXN];16 int dp[MAXN];17 int que[MAXN];18 19 //yk2 - yk1, k1 < k2;20 int getUp(int k1, int k2)21 {22 return dp[k1] - dp[k2];23 }24 25 //xk2 - xk1;26 int getDown(int k1, int k2)27 {28 return sumT[k1] - sumT[k2];29 }30 31 int getDp(int i, int j)32 {33 return dp[j] + (S + sumT[i] - sumT[j]) * sumF[i];34 }35 36 37 int main()38 {39 while (~scanf("%d %d", &N, &S)) {40 dp[N + 1] = sumT[N + 1] = sumF[N + 1] = 0;41 for (int i = 1; i <= N; i++) {42 scanf("%d %d", &t[i], &f[i]);43 }44 for (int i = N; i >= 1; i--) {45 sumT[i] = sumT[i + 1] + t[i];46 sumF[i] = sumF[i + 1] + f[i];47 }48 int head = 0, tail = -1;49 que[++tail] = N + 1;50 for (int i = N; i >= 1; i--) {51 while (head < tail && getUp(que[head + 1], que[head])52 <= sumF[i] * getDown(que[head + 1], que[head])) {53 head++;54 }55 dp[i] = getDp(i, que[head]);56 while (head < tail && getUp(i, que[tail]) * getDown(que[tail], que[tail - 1])57 <= getUp(que[tail], que[tail - 1]) * getDown(i, que[tail])) {58 tail--;59 }60 que[++tail] = i;61 }62 printf("%d\n", dp[1]);63 }64 return 0;65 }
题目链接: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。
于是可以用单调队列来优化!
1 /************************************************************************* 2 > File Name: hdu2993.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月12日 星期三 22时26分13秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 #include <cmath>11 using namespace std;12 13 const int MAXN = (100000 + 100);14 template < class T > inline T getMax(const T &a, const T &b)15 {16 return a > b ? a : b;17 }18 19 int N, K, num[MAXN];20 double sum[MAXN];21 double ans;22 int que[MAXN];23 24 int main()25 {26 while (~scanf("%d %d", &N, &K)) {27 sum[0] = 0;28 for (int i = 1; i <= N; i++) {29 scanf("%d", &num[i]);30 sum[i] = sum[i - 1] + num[i] * 1.0;31 }32 int head = 0, tail = -1;33 que[++tail] = 0;34 ans = 0.0;35 for (int i = K; i <= N; i++) {36 int index = i - K;37 while (head < tail) {38 double y1 = sum[que[tail]] - sum[que[tail - 1]];39 double x1 = que[tail] - que[tail - 1];40 double y2 = sum[index] - sum[que[tail]];41 double x2 = index - que[tail];42 if (y1 * x2 >= y2 * x1) tail--;43 else break;44 }45 que[++tail] = index;46 while (head < tail) {47 double y1 = sum[que[head]] - sum[i];48 double x1 = que[head] - i;49 double y2 = sum[que[head + 1]] - sum[i];50 double x2 = que[head + 1] - i;51 if (y1 * x2 <= y2 * x1) head++;52 else break;53 }54 ans = getMax(ans, (sum[i] - sum[que[head]]) / (i - que[head]) * 1.0);55 }56 printf("%.2lf\n", ans);57 }58 return 0;59 }
题目链接: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值。
1 /************************************************************************* 2 > File Name: hdu2829.cpp 3 > Author: syhjh 4 > Created Time: 2014年03月13日 星期四 19时50分07秒 5 ************************************************************************/ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <algorithm>10 using namespace std;11 12 const int MAXN = (1000 + 10);13 int N, M;14 int num[MAXN], sum[MAXN], w[MAXN]; //w[i]表示1-i算一组的val15 int dp[MAXN][MAXN]; //dp[i][j]表示前j个数分成i组的最小val16 int que[MAXN];17 18 //yk2 - yk1, k1 < k2;19 int getUp(int k1, int k2, int i)20 {21 int yk1 = dp[i - 1][k1] - w[k1] + sum[k1] * sum[k1];22 int yk2 = dp[i - 1][k2] - w[k2] + sum[k2] * sum[k2];23 return yk2 - yk1;24 }25 26 //xk2 - xk127 int getDown(int k1, int k2)28 {29 return sum[k2] - sum[k1];30 }31 32 //dp[i][j] = dp[i - 1][k] + (w[j] - w[k] - sum[k] * (sum[j] - sum[k]));33 int getDp(int i, int j, int k)34 {35 return dp[i - 1][k] + (w[j] - w[k] - sum[k] * (sum[j] - sum[k]));36 }37 38 int main()39 {40 while (~scanf("%d %d", &N, &M)) {41 if (N == 0 && M == 0) break;42 sum[0] = w[0] = 0;43 for (int i = 1; i <= N; i++) {44 scanf("%d", &num[i]);45 sum[i] = sum[i - 1] + num[i];46 w[i] = w[i - 1] + sum[i - 1] * num[i];47 }48 for (int i = 1; i <= N; i++) {49 dp[1][i] = w[i];50 }51 for (int i = 2; i <= M + 1; i++) {52 int head = 0, tail = -1;53 que[++tail] = i - 1;54 for (int j = i; j <= N; j++) {55 while (head < tail && getUp(que[head], que[head + 1], i)56 <= sum[j] * getDown(que[head], que[head + 1])) {57 head++;58 }59 dp[i][j] = getDp(i, j, que[head]);60 while (head < tail && getUp(que[tail], j, i) * getDown(que[tail - 1], que[tail]) <= getUp(que[tail - 1], que[tail], i) * getDown(que[tail], j)) {61 tail--;62 }63 que[++tail] = j;64 }65 }66 printf("%d\n", dp[M + 1][N]);67 }68 return 0;69 }70 71 72
PS:单调队列做多了,就能发现只要推出递推方程,然后转化为斜率,那么剩下的基本上就是模板题了!
- 【专题】单调队列/斜率优化DP
- 单调队列,斜率优化dp 专题
- 斜率优化+单调队列优化DP
- 斜率优化+单调队列优化DP<转>
- 单调队列+斜率优化的DP
- 【专辑】单调队列+斜率优化的DP
- 【整理】斜率or单调队列优化dp
- ##单调队列、斜率优化##
- 关于DP的单调队列优化和斜率优化区别
- 模型化理解单调队列优化和斜率优化DP
- dp的斜率优化与单调队列优化
- 【DP】【斜率优化】【单调队列优化】Gift 题解
- hdu3507 Print Article 单调队列斜率优化DP
- 【GDOI2014模拟】服务器 (斜率单调队列优化Dp)
- [HNOI2008]玩具装箱-DP斜率优化-单调队列-学习笔记
- 单调队列优化专题
- 斜率优化dp专题学习
- dp部分总结(单调队列,四边形优化,斜率优化,树形dp)
- spfa求最长路
- FANN的图形用户界面形式
- hdu 3001(状压dp, 3进制)
- zoj 3471(状态压缩)
- 汇编考试小结
- 单调队列,斜率优化dp 专题
- zoj 3644(dp + 记忆化搜索)
- 线段树专题训练
- 并查集中的合并、删除操作
- 类模板Queue的实现
- poj 1816 (Trie + dfs)
- AC自动机专题
- wireshark的严格检查,如tcp的校验码检查
- Mongodb集群搭建的三种方式