题目大意:将一个非负整数序列切成连续的若干段,使得每一段的和均不超过一定值,在此基础上最小化每一段的最大值的和。
思路:O(N2)的动态规划方程非常明显:
fi=min0≤j<i{fj+maxj+1≤k≤i{wk}}
这样利用RMQ的预处理即可做到O(N^2).
不过这样显然是不能AC的。
我们考虑能够进行什么优化。
我们发现以下性质:
(1)fi≤fj(1≤i<j≤n)
这个性质是显然的,就不进行证明。
(2)在对i进行决策时,若有j满足wj≤maxj+1≤k≤i{wk}
那么我们可以选择一个更小的k=j-1,让j进入最后一段。这样做不会使答案变大,因为fk≤fj
并且后一段的最大值显然相同,因此决策k不会劣于决策j.我们可以不断向前选择尽量小的k,直到
wk>maxk+1≤p≤i{wp}
此时,如果k再向左移动,最后一段的最大值就会增大了。
我们可以发现,合理的决策点只有我们维护的队头到队尾权值递减(队头权值最大)单调队列中的目前合法位置。
画画图就知道,中间的“夹缝”中的决策点都被我们用刚才的“移动”给否弃掉了。
但是,目前的这些决策点中哪些是比较优的呢?我们不知道,因此我们需要在队列中分别计算决策值并取最优解。
随机数据下,这是个大大的优化,单调队列中的点数不会很多。
求后一段的最大值时不用RMQ,单调队列中的下一个元素就是后一段的最大值。
对i进行决策时,首先将i加入单调队列,然后枚举队列中的决策点进行转移。但是这样并没有包含所有情况!还有可能是后一段的最大值恰为队头!
这种情况我们只需一开始利用单调队列看一下以每个点为结尾的一段最远向左拓展到哪里,直接用那个点作为决策点即可。
这样我们就完美的解决了这个问题。
有强迫症?觉得复杂度不行?
那就像我一样写个堆吧!
细节较多,用心体会。
Code:
#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL INF = 1LL << 60;
#define N 100010
int w[N], pre[N], q[N], f, t;
struct Node {
int lab;
LL v;
Node(){}
Node(int _lab, LL _v):lab(_lab),v(_v){}
bool operator < (const Node &B) const {
return v < B.v || (v == B.v && lab < B.lab);
}
};
struct Heap {
Node a[N];
int top, ins[N];
Heap():top(0){}
inline void Swim(int x) {
for(; x != 1; x >>= 1)
if (a[x] < a[x>>1])
swap(ins[a[x].lab],ins[a[x>>1].lab]),swap(a[x],a[x>>1]);
else
break;
}
inline void Sink(int x) {
int son;
for(; (x << 1) <= top; ) {
son=(((x<<1)==top)||(a[x<<1]<a[x<<1^1]))?x<<1:x<<1^1;
if (a[son] < a[x])
swap(ins[a[son].lab],ins[a[x].lab]),swap(a[x],a[son]),x=son;
else
break;
}
}
void Insert(const Node &x) {
a[++top] = x;
ins[x.lab] = top;
Swim(top);
}
void Modify(int x, long long _v) {
if (_v > a[ins[x]].v) {
a[ins[x]].v = _v;
Sink(ins[x]);
}
else {
a[ins[x]].v = _v;
Swim(ins[x]);
}
}
Node Min() {
return a[1];
}
}H;
long long dp[N], add[N];
int main() {
#ifndef ONLINE_JUDGE
//freopen("tt.in", "r", stdin);
#endif
int n;
LL M;
cin >> n >> M;
register int i, j;
bool Nosol = 0;
for(i = 1; i <= n; ++i) {
scanf("%d", &w[i]);
add[i] = add[i - 1] + w[i];
if (w[i] > M)
Nosol = 1;
}
if (Nosol) {
puts("-1");
return 0;
}
f = t = 0;
q[t++] = 0;
LL now = 0;
for(i = 1; i <= n; ++i) {
now += w[i];
while(f < t && now > M) now -= w[q[f]], ++f;
q[t++] = i;
pre[i] = q[f];
}
dp[0] = 0;
f = t = 0;
for(i = 1; i <= n; ++i)
H.Insert(Node(i, INF));
for(i = 1; i <= n; ++i) {
while(f < t && w[q[t - 1]] <= w[i]) H.Modify(q[t - 1], INF), --t;
q[t++] = i;
if (f + 1 != t)
H.Modify(q[t - 2], dp[q[t - 2]] + w[i]);
while(f < t && add[i] - add[q[f] - 1] > M) H.Modify(q[f], INF), ++f;
dp[i] = INF;
if (H.Min().v != INF)
dp[i] = H.Min().v;
dp[i] = min(dp[i], dp[pre[i] - 1] + w[q[f]]);
}
cout << dp[n];
return 0;
}
细节较多,用心体会。