一类斜率优化的dp(特有性质:只能连续,不能交叉)
给定一个有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\n",k, dp[c][n]); 75 } 76 return 0; 77 }
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\n", dp[n]); 73 } 74 return 0; 75 }