一类斜率优化的dp(特有性质:只能连续,不能交叉)

hdu3480

给定一个有n个数的集合,将这个集合分成m个子集,要求子集的并等于全集
求花费最小。

花费为该子集的(最大数-最小数)的平方。

我们将n个数排序,

a < b < c < d

那么不可能a,c一个集合,b,c一个集合

明显a,b一个集合,c,d一个集合更优

也就是说某一个数只能和它前面的连续几个数合起来形成一个子集。 正是因为有这个性质才能dp

dp[i][j]表示第j个数在第i个集合的最小花费

dp[i][j] = min(dp[i][j],dp[i-1][k]) 1<=k<j

dp[i-1][k1] + (a[j]-a[k1+1])^2 < dp[i-1][k2]+(a[j]-a[k2+1])^2

dp[i-1][k1]+a[k1]^2-(dp[i-1][k2]+a[k2]^2) < 2a[j]*(a[k1+1]-a[k2+1])

这样就能够用斜率优化了,由于是求最小值,所以维护一个下凸包就行了。

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <algorithm>
 5 #include <iostream>
 6 #include <queue>
 7 #include <stack>
 8 #include <vector>
 9 #include <map>
10 #include <set>
11 #include <string>
12 #include <math.h>
13 using namespace std;
14 #pragma warning(disable:4996)
15 #pragma comment(linker, "/STACK:1024000000,1024000000")
16 typedef int LL;                   
17 const int INF = 1<<30;
18 /*
19 给定一个有n个数的集合,将这个集合分成m个子集,要求子集的并等于全集
20 求花费最小,  花费为什么每个集合的(最大值-最小值)的平方
21 
22 dp[i][j]表示第j个数在第i个集合的最小花费
23 用滚动数组压缩空间
24 */
25 
26 const int N = 10000 + 10;
27 const int M = 5000 + 10;
28 LL a[N];
29 LL dp[2][N];
30 int q[N], head, tail;
31 LL dw(LL a, LL b)
32 {
33     return (a - b)*(a - b);
34 }
35 LL getUp(int k1, int k2, int c)
36 {
37     return dp[c][k1] + a[k1 + 1] * a[k1 + 1] - (dp[c][k2] + a[k2 + 1] * a[k2 + 1]);
38 }
39 LL getDown(int k1, int k2)
40 {
41     return 2 * (a[k1 + 1] - a[k2 + 1]);
42 }
43 int main()
44 {
45     int t, n, m;
46     scanf("%d", &t);
47     for (int k = 1; k <= t; ++k)
48     {
49         scanf("%d%d", &n, &m);
50         for (int i = 1; i <= n; ++i)
51             scanf("%d", &a[i]);
52         sort(a + 1, a + n + 1);
53         n = unique(a + 1, a + n + 1) - a - 1;
54         if (m > n)
55             m = n;
56         int c = 0;
57         for (int j = 1; j <= n; ++j)
58             dp[0][j] = dw(a[j], a[1]);
59         for (int i = 2; i <= m; ++i)
60         {
61             head = tail = 0;
62             for (int j = i; j <= n; ++j)
63             {
64                 while (head +1 < tail && getUp(j - 1, q[tail - 1], c)*getDown(q[tail - 1], q[tail - 2])
65                     <= getUp(q[tail - 1], q[tail - 2], c)*getDown(j - 1, q[tail - 1]))
66                     tail--;
67                 q[tail++] = j - 1;
68                 while (head + 1 < tail&&getUp(q[head + 1], q[head], c) < a[j] * getDown(q[head+1], q[head]))
69                     head++;
70                 dp[c ^ 1][j] = dp[c][q[head]] + dw(a[j], a[q[head] + 1]);
71             }
72             c ^= 1;
73         }
74         printf("Case %d: %d
",k, dp[c][n]);
75     }
76     return 0;
77 }
View Code

hdu3405

n头牛,分成几组,每组至少有k头牛
要求费用最小,

费用是每组所有牛的moo,减去该组中最小的moo

将moo排序

那么如果是分成两组的话,那么不可能是a,c一组, b,d一组
a,b,一组比c,d一组肯定更优。
又遇到了有这种性质的题目, 连续比交叉更优,这样,才能用dp来解决,

dp[j]表示以j结尾的team的最小花费
(dp[k1]-sum[k1]+k1*moo[k1+1]) - (dp[k2]-sum[k2]+k2*moo[k2+1]) < i*(moo[k1+1]-moo[k2+1])
dp[k1]+k1*moo[k1+1] - (dp[k2]+k2*moo[k2+1]) < i * (moo[k1+1]-moo[k2+1])
所以维护递增的斜率

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <algorithm>
 5 #include <iostream>
 6 #include <queue>
 7 #include <stack>
 8 #include <vector>
 9 #include <map>
10 #include <set>
11 #include <string>
12 #include <math.h>
13 using namespace std;
14 #pragma warning(disable:4996)
15 #pragma comment(linker, "/STACK:1024000000,1024000000")
16 typedef __int64 LL;                   
17 const int INF = 1<<30;
18 /*
19 n头牛,分成几组,每组至少有k头牛
20 要求费用最小,
21 费用是每组所有牛的moo,减去该组中最小的moo
22 a < b < c < d
23 那么如果是分成两组的话,那么不可能是a,c一组, b,d一组
24 a,b,一组比c,d一组肯定更优。
25 又遇到了有这种性质的题目, 连续比交叉更优,这样,才能用dp来解决,
26 dp[j]表示以j结尾的team的最小花费
27 (dp[k1]-sum[k1]+k1*moo[k1+1]) - (dp[k2]-sum[k2]+k2*moo[k2+1]) < i*(moo[k1+1]-moo[k2+1])
28 dp[k1]+k1*moo[k1+1] - (dp[k2]+k2*moo[k2+1]) < i * (moo[k1+1]-moo[k2+1])
29 所以维护递增的斜率
30 
31 */
32 const int N = 400000 + 10;
33 LL moo[N], dp[N], sum[N];
34 int q[N], head, tail;
35 
36 LL getUp(int k1, int k2)
37 {
38     return dp[k1] + k1*moo[k1 + 1] - sum[k1] - (dp[k2] + k2*moo[k2 + 1] - sum[k2]);
39 }
40 LL getDown(int k1, int k2)
41 {
42     return moo[k1 + 1] - moo[k2 + 1];
43 }
44 int main()
45 {
46     int n, t;
47     while (scanf("%d%d", &n, &t) != EOF)
48     {
49         for (int i = 1; i <= n; ++i)
50         {
51             scanf("%I64d", &moo[i]);
52         }
53         sort(moo + 1, moo + n + 1);
54         for (int i = 1; i <= n; ++i)
55             sum[i] = moo[i] + sum[i - 1];
56         for (int i = t; i <= n; ++i)
57             dp[i] = sum[i] - i*moo[1];
58 
59         head = tail = 0;
60         q[tail++] = 0;
61         q[tail++] = t;
62         int k = t + 1;
63         for (int i = 2*t; i <= n; ++i)
64         {
65             while (head + 1 < tail&&getUp(q[head + 1], q[head]) < i*getDown(q[head + 1], q[head]))
66                 head++;
67             dp[i] = dp[q[head]] + sum[i]-sum[q[head]] - (i-q[head])*moo[q[head] + 1];
68             while (head + 1 < tail&&getUp(k, q[tail - 1])*getDown(q[tail - 1], q[tail - 2]) <= getUp(q[tail - 1], q[tail - 2])*getDown(k, q[tail - 1]))
69                 tail--;
70             q[tail++] = k++;
71         }
72         printf("%I64d
", dp[n]);
73     }
74     return 0;
75 }
View Code
原文地址:https://www.cnblogs.com/justPassBy/p/4741399.html