bzoj2448 挖油

题目链接

题面

思路

先考虑\(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;
}
posted @ 2019-01-24 12:44  wxyww  阅读(171)  评论(0编辑  收藏  举报