DP + 单调队列优化 + 平衡树 好题


Description

Given an integer sequence { an } of length N, you are to cut the sequence into several parts every one of which is a consecutive subsequence of the original sequence. Every part must satisfy that the sum of the integers in the part is not greater than a given integer M. You are to find a cutting that minimizes the sum of the maximum integer of each part.

Input

The first line of input contains two integer N (0 < N ≤ 100 000), M. The following line contains N integers describes the integer sequence. Every integer in the sequence is between 0 and 1 000 000 inclusively.

Output

Output one integer which is the minimum sum of the maximum integer of each part. If no such cuttings exist, output −1.

Sample Input

8 17

2 2 2 8 1 8 2 1

Sample Output

12

Hint

Use 64-bit integer type to hold M.


题目大意

给你一个长度为 n 序列,要你把它分成不同块,使得每块最大值的和最小,每块内数字的和不超过 m。题意很简单。

题解

这道题是一道非常好的单调队列优化 DP 的题。首先,我们忽略掉 m 这个条件,我们可以很直接地看出,可以设 dp[i] 为前 i 个数可以得到的最优答案。转移方程为:

dp[i] = min{ dp[j] + max{a[j+1], a[j+2] ... a[i]} };

朴素的转移时间复杂度为\(O(n^2)\), 所以我们考虑用某种方法来优化掉多出来的这个\(n\)。

首先我们来看怎样优化求区间内最大值,我们可以想到用rmq,但很快就被否定,再一想,单调队列!!

很明显,dp[j] 是单调不下降的,所以,当当前块内最大值确定时,j 越小越好,我们可以模拟边界 j 由后向前推进时的情况,如果 a[j] 大于了当前的最大值,那么,当前块内的max值会增大,否则不变。

这个不变就为我们提供了优化的前提,我们可以根据把一段不变的区间压缩成只更新一次,这样,我们就去掉了多余的决策。

于是,我们维护一个单调队列 q[], q[] 中维护的是单调下降的下标,

如样例 对a[4~8] {8,1,8,2,1},i = 8, 单调队列中存的值是{6,7,8},返回a[]中的值为{8,2,1}。

当 4 <= j <= 6 时,max{a[j] ... a[i]} 都等于 8;

当7 <= j <= 7 时,max{...}等于 2;

当8 <= j <= 8 时,max{...}等于 1;

然后我们加上限制条件 m,我们只用从单调队列头部删除不满足要求的值即可。

然后我们只用从这几个点来更新,是不是优化了很多?

等一等,如果数组a[] 本身就是单调下降的怎么办,在这种情况下,时间复杂度依然可以高达\(O(n^2)\)。不用急,我们再继续往下探究,看是否可以在\(O(1)\)或\(O(log_n)\)的复杂度内快速找到使答案最小的那个 j 。

对于一个 j,如果其在更新 dp[i - 1] 时就已经出现,并且在更新 dp[i] 时没有被删除,那么可以得到:

dp[j-1] + max{a[j] ... a[i-1]} == dp[j-1] + max{a[j] ... a[i]}

即,我们可以用本来是更新 dp[i - 1] 的 j 来继续更新 dp[i]。

所以我们可以将 dp[j-1] + max{a[j] ... a[i]} 用一颗平衡树来维护,每次直接取出最小值来更新 dp[i] 即可。

于是这道题就完美解决了,时间复杂度为\(O(nlog_n)\)。

但是因为数据太弱,我们用 STL 中的 set 就可以通过,set 不仅常数太大,并且erase是直接在树中删掉某个数,而不是使值减一,这样,如果出现两个相同的数同时出现在 set 中,在删除时就会导致答案错误。所以,正解应该手写一颗平衡树。

更有甚者,理论复杂度为\(O(n^2)\)的不用平衡树的算法居然比手写平衡树还要快。。。poj你数据是有多水 2333


UPD: 之前有一点错了, set确实是不行, 但是STL中还提供了multiset多元集合,支持多个相同元素。用法和set几乎一样。

下面是\(O(n^2)\)和\(O(nlog_n)\)两份代码。

\(O(n^2)\)代码

  1. #include <iostream>
  2. #include <set>
  3. #include <cstdio>
  4. using namespace std;
  5. typedef long long LL;
  6. const int maxn = 1e5 + 5;
  7. int n;
  8. LL m;
  9. LL a[maxn],sum[maxn];
  10. int q[maxn];
  11. LL dp[maxn];
  12. int main() {
  13. ios::sync_with_stdio(false); cin.tie(0);
  14. while(scanf("%d%lld",&n,&m) != EOF) {
  15. int ok = 0;
  16. for(int i = 1;i <= n;i++) {
  17. scanf("%lld",a+i);if(a[i] > m) ok = 1;
  18. sum[i] = a[i] + sum[i-1];
  19. }
  20. int p = 1,front = 0,tail = -1;
  21. for(int i = 1;i <= n;i++) {
  22. if(ok)break;
  23. while(sum[i] - sum[p-1] > m)p++;
  24. while(front <= tail && a[q[tail]] <= a[i]) tail--;
  25. q[++tail] = i;
  26. while(q[front] < p) front++;
  27. dp[i] = dp[p-1] + a[q[front]];
  28. for(int j = front;j < tail;j++) dp[i] = min(dp[i],dp[q[j]] + a[q[j+1]]);
  29. }
  30. if(ok) dp[n] = -1;
  31. printf("%lld\n",dp[n]);
  32. }
  33. return 0;
  34. }

\(O(nlog_n)\)代码

  1. #include <iostream>
  2. #include <set>
  3. #include <cstdio>
  4. using namespace std;
  5. typedef long long LL;
  6. const int maxn = 1e5 + 5;
  7. int n;
  8. LL m;
  9. LL a[maxn],sum[maxn];
  10. int q[maxn];
  11. LL dp[maxn];
  12. multiset <LL> s;
  13. int main() {
  14. ios::sync_with_stdio(false); cin.tie(0);
  15. while(scanf("%d%lld",&n,&m) != EOF) {
  16. int ok = 0;
  17. for(int i = 1;i <= n;i++) {
  18. scanf("%lld",a+i);if(a[i] > m) ok = 1;
  19. sum[i] = a[i] + sum[i-1];
  20. }
  21. int p = 1,front = 0,tail = -1;
  22. s.clear();
  23. for(int i = 1;i <= n;i++) {
  24. if(ok)break;
  25. while(sum[i] - sum[p-1] > m)p++;
  26. while(front <= tail && a[q[tail]] <= a[i]) {
  27. if(front < tail) s.erase(dp[q[tail-1]] + a[q[tail]]);
  28. tail--;
  29. }
  30. q[++tail] = i;
  31. if(front < tail) s.insert(dp[q[tail-1]] + a[i]);
  32. while(q[front] < p) {
  33. if(front < tail) s.erase(dp[q[front]] + a[q[front+1]]);
  34. front++;
  35. }
  36. dp[i] = dp[p-1] + a[q[front]];
  37. if(front < tail) dp[i] = min(dp[i], *s.begin());
  38. }
  39. if(ok) dp[n] = -1;
  40. printf("%lld\n",dp[n]);
  41. }
  42. return 0;
  43. }

[poj3017] Cut the Sequence (DP + 单调队列优化 + 平衡树优化)的更多相关文章

  1. POJ-3017 Cut the Sequence DP+单调队列+堆

    题目链接:http://poj.org/problem?id=3017 这题的DP方程是容易想到的,f[i]=Min{ f[j]+Max(num[j+1],num[j+2],......,num[i] ...

  2. poj 3017 Cut the Sequence(单调队列优化DP)

    Cut the Sequence \(solution:\) 这道题出的真的很好,奈何数据水啊! 这道题当时看得一脸懵逼,说二分也不像二分,说贪心也不像贪心,说搜索吧这题数据范围怎么这么大?而且这题看 ...

  3. POJ 3017 Cut the Sequence (单调队列优化DP)

    题意: 给定含有n个元素的数列a,要求将其划分为若干个连续子序列,使得每个序列的元素之和小于等于m,问最小化所有序列中的最大元素之和为多少?(n<=105.例:n=8, m=17,8个数分别为2 ...

  4. poj 3017 Cut the Sequence(单调队列优化 )

    题目链接:http://poj.org/problem?id=3017 题意:给你一个长度为n的数列,要求把这个数列划分为任意块,每块的元素和小于m,使得所有块的最大值的和最小 分析:这题很快就能想到 ...

  5. 3622 假期(DP+单调队列优化)

    3622 假期 时间限制: 1 s 空间限制: 64000 KB 题目等级 : 黄金 Gold 题目描述 Description 经过几个月辛勤的工作,FJ决定让奶牛放假.假期可以在1-N天内任意选择 ...

  6. DP+单调队列 codevs 1748 瑰丽华尔兹(还不是很懂具体的代码实现)

    codevs 1748 瑰丽华尔兹 2005年NOI全国竞赛  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 大师 Master 题解       题目描述 Descripti ...

  7. APIO2010特别行动队(单调队列、斜率优化)

    其实这题一看知道应该是DP,再一看数据范围肯定就是单调队列了. 不过我还不太懂神马单调队列.斜率优化…… 附上天牛的题解:http://www.cnblogs.com/neverforget/arch ...

  8. hdu4374One hundred layer (DP+单调队列)

    http://acm.hdu.edu.cn/showproblem.php?pid=4374 去年多校的题 今年才做 不知道这一年都干嘛去了.. DP的思路很好想 dp[i][j] = max(dp[ ...

  9. 习题:烽火传递(DP+单调队列)

    烽火传递[题目描述]烽火台又称烽燧,是重要的防御设施,一般建在险要处或交通要道上.一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息:夜晚燃烧干柴,以火光传递军情.在某两座城市之间有n个烽火台,每个烽火台 ...

随机推荐

  1. php数组序列化serialize与unserialize

    $arr=array('1','2','3');echo serialize($arr); //序列化 a:3:{i:0;s:1:"1";i:1;s:1:"2" ...

  2. IOS第11天(3:UIPickerView省市联动)

    ********* #import "ViewController.h" #import "Province.h" @interface ViewControl ...

  3. 【iCore3 双核心板】例程二十六:MODBUS TCP实验——电源监控

    实验指导书及代码包下载: http://pan.baidu.com/s/1pKhxKd9 iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

  4. NGUI 渲染流程深入研究 (UIDrawCall UIGeometry UIPanel UIWidget)

    上图是一个简要的NGUI的图形工作流程,UIGeometry被UIWidget实例化之后,通过UIWidget的子类,也就是UISprit,UILabel等,在OnFill()函数里算出所需的Geom ...

  5. android 一个简单的服务例子

    public class MessageService extends Service { // 获取消息线程 private MessageThread messageThread = null; ...

  6. Java 集合快速失败异常

    快速失败 在JDK中,查看集合有很多关于快速失败的描述: 注意,此实现不是同步的.如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步.(结构上的修改是指添 ...

  7. __weak

    需要使用弱引用的 三种情况: 1. 如果这个block不被持有,那么你完全没有必要使用__weak 2. 如果被持有了,那么__weak是必然的 3. 如果在多线程并发的情况下,不仅要使用__weak ...

  8. 根据value选择select

    <script> var tem="{$Zgoods.type_2}"; $("#type_2 option[value='"+tem+" ...

  9. jedis例子

    @Test public void testDiscoverNodesAutomatically(){ Set<HostAndPort> jedisClusterNode=new Hash ...

  10. 安装vim

    命令安装vim sudo apt-get install vim