题面
思路
先考虑(n leq 100)的做法。
区间dp。
状态。用(f[l][r])表示知道l到r内(x)的位置最少需要的时间
转移。枚举一个(l leq k leq r)。那么现在我们要在k处挖油了,然后我们根据k处有没有油再去确定下次是挖([k + 1,r])还是([l,k-1])。因为是在最坏情况下,所以我们挖完之后肯定是去(f[l][k - 1])与(f[k + 1][r])之间较大的那个去挖。所以转移方程就是$$f[l][r] = min_{k=l}^r{max(f[l][k-1],f[k+1][r]) + w[k]}$$
然后考虑优化上面的区间dp
比较显然的一个性质就是(f[l][r] leq f[l][r+1],f[l][r] leq f[l-1][r])因为区间更大肯定不会使探测时间更少。
然后观察上面的dp还有那些优化空间。发现枚举k的这一层循环是很浪费的。所以考虑能不能快速找出转移位置。
然后继续观察那个暴力式子。发现我们从左到右枚举k的过程中,(f[l][k-1])单调不降,(f[k+1][r])单调不升,所以里面那个max肯定是前面一段全是的(f[k+1][r])后面一段全是(f[l][k-1])。
所以呢,肯定可以找到一个分界点(g),使得(g)前面从(f[l][k-1])转移,后面从(f[k+1][r])转移。
然后怎么的到这个(g)呢。考虑我们固定l端点,然后从左向右枚举r端点。这时因为(g)右边在增大,所以(g)肯定是不断往右移动的。
然后就可以用线段树来求区间最小值,并维护(g)。复杂度是(O(n^2logn))
考虑继续优化
其实可以用单调队列来优化。在向右枚举(r)的过程中,因为(g)是单调不降的,所以左边的点肯定会先失去转移的价值,所以维护出满足(f[l][k-1]+w[k]>f[k+1][r]+w[k])的点中的最大值,并且在不满足上面条件时出队。就可以了。这是以固定左端点考虑左边为例。固定右端点考虑右边也一样,只不过(r)是不断的跳的,所以要用n个单调队列来维护出(n)个(r)端点时的转移位置。然后从这两个转移方案中选个更优的就行了。
代码
/*
* @Author: wxyww
* @Date: 2019-01-19 17:57:44
* @Last Modified time: 2019-01-19 19:28:03
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<bitset>
using namespace std;
typedef long long ll;
const int N = 2000 + 10;
#define A(x) f[l][x - 1] + w[x]
#define B(x) f[x + 1][r] + w[x]
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
ll f[N][N];
int w[N],q[N][N],T[N],H[N];
int main() {
int n = read();
for(int i = 1;i <= n;++i) w[i] = read();
for(int l = n;l >= 1;--l) {
H[0] = 1;
T[0] = 1;
f[l][l] = w[l];
q[0][1] = l;
T[l] = H[l] = 1;
q[l][1] = l;
for(int r = l + 1;r <= n;++r) {
while(T[0] >= H[0] && A(q[0][H[0]]) < B(q[0][H[0]])) ++H[0];
while(T[0] >= H[0] && A(r) < A(q[0][T[0]])) --T[0];
q[0][++T[0]] = r;
while(T[r] >= H[r] && B(q[r][H[r]]) < A(q[r][H[r]])) ++H[r];
while(T[r] >= H[r] && B(l) < B(q[r][T[r]])) --T[r];
q[r][++T[r]] = l;
f[l][r] = min(A(q[0][H[0]]),B(q[r][H[r]]));
}
}
cout<<f[1][n];
return 0;
}