描述
把一个正数列 $A$分成若干段, 每段之和 不超过 $M$, 并且使得每段数列的最大值的和最小, 求出这个最小值。
题解
首先我们可以列出一个$O(n^2)$ 的转移方程 : $F_i = min( F_j + max( A_k ) ) $ $ j < i && j < k <= i$
然后我们可以考虑毒瘤优化。
按照lyd的书中的思路, j 想要成为 可能的最优决策, 必须满足两个条件之一 :
- j 是最小的使 $sumlimits_{k= j + 1}^ia_k <= M$成立的数
- $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 "), 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 ", f[n]); 59 }