cychester

poj3017 Cut the Sequence 单调队列 + 堆 dp

描述

  把一个正数列 $A$分成若干段, 每段之和 不超过 $M$, 并且使得每段数列的最大值的和最小, 求出这个最小值。

                题目链接

 

题解

  首先我们可以列出一个$O(n^2)$ 的转移方程 : $F_i = \min( F_j + \max( A_k ) )  $ $ j < i  \&\&   j < k <= i$

  然后我们可以考虑毒瘤优化。

  按照lyd的书中的思路, j 想要成为 可能的最优决策, 必须满足两个条件之一 :

  1.   j 是最小的使    $\sum\limits_{k= j + 1}^ia_k  <= M$成立的数
  2.        $\forall k \in [j + 1, i] , A_j>A_k$

  可以用反证法来证明。

  对于第一个条件,可以在$O(n)$ 时间内求出所有的$j$, 并进行决策。

  接着构造一个单调队列, 满足 $j$ 递增, $A_j$ 递减 ——  若 $A[ j_1]< A[j _2]$则不满足第二个性质, 只能由让$j$ 满足第一个条件, 将$ j_1$弹出队列即可。

  查询在队列中的最优决策时, 队首不一定就是最有决策, 需要用 STL- set 来储存队列中的 $ F_j + \max(A_k)$ $ j < i && j < k <=i$, 查询set中的最小值并更新答案。

  而 $F_j$是已经求出的,最后的问题就是如何快速求出 $\max(A_k)$ 。 单调队列中的 元素 $j$的下一个元素就是要求的$\max(A_k)$。 因为单调队列中$A_j$是递减的。  

  另外还有许多细节需要注意,看代码吧(

 

 

代码

 1 #include<cstring>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<set>
 5 #define rep(i,a,b) for( int i = (a); i <= (b); ++i )
 6 #define per(i,a,b) for( int i = (a); i >= (b); --i )
 7 #define rd read()
 8 using namespace std;
 9 typedef long long ll;
10 
11 const int N = 2e5 + 1e4;
12 
13 int n, a[N], q[N];
14 ll f[N], m;
15 
16 set<ll>st;
17 
18 int read() {
19     int X = 0, p = 1; char c = getchar();
20     for(; c > '9' || c < '0'; c = getchar() ) if( c == '-' ) p = -1;
21     for(; c >= '0' && c <= '9'; c = getchar() ) X = X * 10 + c - '0';
22     return X * p;
23 }
24 
25 inline ll cmin( int A ,int B ) {
26     return A < B ? A : B;
27 }
28 
29 int main()
30 {
31     n = rd;
32     scanf("%lld",&m);
33     rep( i, 1, n ) a[i] = rd;
34     f[0] = 0;
35     int low = 0, l = 1, r = 0;
36     ll sum = 0;
37     set<ll>::iterator it;
38     rep( i, 1, n ) {
39         sum += a[i];
40         while( sum > m ) sum -= a[++low]; // 求出最小的j使得连续一段和不超过m
41         if( low >= i ) return printf("-1\n"), 0;
42         while( l <= r && q[l] < low ) {//检验队首是否满足连续和不超过m
43             if( l < r ) st.erase( f[q[l]] + a[q[l+1]]);//队列中删除被弹出的答案
44             l++;
45         }
46         while( l <= r && a[q[r]] <= a[i] ) {//使队列递减
47             if( l < r ) st.erase( f[q[r - 1]] + a[q[r]]);
48             r--;
49         }
50         q[++r] = i;
51         if( l < r ) st.insert( f[q[r - 1]] + a[q[r]]);//加入i,这样才能更新出可行的最优答案
52         f[i] = f[low] + a[q[l]];
53         if( st.size() ) {
54             it = st.begin();
55             f[i] = cmin( *it, f[i]);
56         }
57     }
58     printf("%lld\n", f[n]);
59 }
View Code

 

posted on 2018-08-15 12:41  cychester  阅读(180)  评论(0编辑  收藏  举报

导航