一类斜率优化的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\n",k, dp[c][n]);
75     }
76     return 0;
77 }

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\n", dp[n]);
73     }
74     return 0;
75 }

时间: 2024-10-02 21:11:48

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

队列优化和斜率优化的dp

可以用队列优化或斜率优化的dp这一类的问题为 1D/1D一类问题 即状态数是O(n),决策数也是O(n) 单调队列优化 我们来看这样一个问题:一个含有n项的数列(n<=2000000),求出每一项前面的第m个数到它这个区间内的最小值 可以使用RMQ求区间最小值,那么时间复杂度是O(nlogn),不是让人很满意. dp[i]为i-m+1->i这个区间的最小值. 那么状态转移方程是 可以看出,这个题目的状态数是O(n),决策数是O(m),且决策的区间是连续的,那么可以尝试想办法把O(m)优化成O(

[NOI2014]购票 --- 斜率优化 + 树形DP + 数据结构

题目描述 今年夏天,NOI在SZ市迎来了她30周岁的生日. 来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会. 全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接. 为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号.其中SZ市的编号为 1. 对于除SZ市之外的任意一个城市 v,我们给出了它在这棵树上的父亲城市 fv 以及到父亲城市道路的长度 sv. 从城市 v 前往SZ市的方法为:选择城市 v 的一个祖先 a,支付购票的费用,乘坐交通工具到

【BZOJ-1597】土地购买 DP + 斜率优化

1597: [Usaco2008 Mar]土地购买 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2931  Solved: 1091[Submit][Status][Discuss] Description 农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足(1 <= 宽 <= 1,000,000; 1 <= 长 <= 1,000,000). 每块土地

斜率优化dp小结

单调队列优化 在写斜率优化之前,我们来回顾一下单调队列优化的dp 1. 对于如下形式的dp方程 dp[i]=min{dp[j]+f(j)}(0<j<i) 我们直接用一个变量维护(0, i)中dp[j] + f(j)的最小值即可 2.对于如下形式的dp方程 dp[i]=min{dp[j]+f(j)}(i?m<j<i) 我们可以用一个单调队列维护一个(i - m, j)中dp[j] + f(j)的最小值,然后做到O(1)转移. 斜率优化 基本形式 但是对于形如 dp[i]=min{dp

HNOI2008玩具装箱 (斜率优化)

总算A了,心情好激动-- 如果会了一类斜率优化,基本上这类题就成了套模版了-- 只是k函数不同 1 var n,l,x,tail,head,m:int64; 2 i,j:longint; 3 dp,q,s:array[0..100000] of int64; 4 function k(x,y:longint):double; 5 begin 6 k:=1.0*((dp[x]+s[x]*s[x]-dp[y]-s[y]*s[y])/(s[x]-s[y])); 7 end; 8 procedure m

动态规划(斜率优化):BZOJ 3675 [Apio2014]序列分割

Description 小H最近迷上了一个分割序列的游戏.在这个游戏里,小H需要将一个长 度为N的非负整数序列分割成k+l个非空的子序列.为了得到k+l个子序列, 小H将重复进行七次以下的步骤: 1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的 序列一一也就是一开始得到的整个序列): 2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新 序列. 每次进行上述步骤之后,小H将会得到一定的分数.这个分数为两个新序 列中元素和的乘积.小H希望选择一种最佳的分割方案,使得

[Bzoj1096][ZJOI2007]仓库建设(斜率优化)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1096 一开始想了想费用流,然后被数据范围pass掉了,感觉dp更可行一些. 只想到一个O(n2)的做法,看到式子比较复杂,就感觉像是斜率优化. dp[i]表示前i个工厂所求的最小费用,则第i个工厂一定会建一个仓库.转移方程为$dp[i]=min(dp[j]+\sum_{k=j}^{i-1}p[k]*(x[i]-x[k]))+c[i]$. 将式子展开,用sump表示p数组的前缀和,sum

【笔记篇】斜率优化dp(四) ZJOI2007仓库建设

传送门戳这里>>> \(n\leq1e6\), 显然还是\(O(n)\)的做法. 这个题有个条件是只能运往编号更大的工厂的仓库, 这也是写出朴素dp的方程的条件. 我们令\(f[i]\)表示前\(i\)个工厂的最小花费, 那么易得 \[f[i]=min\{f[j]+t(j,i)\}\] 其中这个\(t(j,i)\)表示将\((j,i)\)这个区间的东西运到\(i\)的总费用. 很显然, 这个式子要\(O(1)\)求出来才行, 不然复杂度就要炸... 那么怎么\(O(1)\)求呢? 考虑类

hdu3507 Print Article[斜率优化dp入门题]

Print Article Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)Total Submission(s): 11761    Accepted Submission(s): 3586 Problem Description Zero has an old printer that doesn't work well sometimes. As it is antiqu