Cut the Sequence

题意是给你一个序列,找出一个连续的划分,使得每部分和小于M ,然后每一部分贡献出最大的值作为该部分的权值,求权值和的最小值。 数据大小是100000
dp方程很容易想到:
dp[i] = min(dp[j]+ max[j+1,i]); j<i;
然而这个时间复杂度是n2的。一般情况是要用一种数据结构来优化,首先容易想到的就是线段树,然而i变化的时候不能保证原来的最大值不变。然后考虑能不能优化状态。 对于现在的状态dp[now] , 设 i>j , 则此时i和j对应的转化式子为
dp[i]+max[i+1,now] ;
dp[j]+max[j+1,now] ;
由于有dp[i]>=dp[j] (显而易见)具有单调性;
此时max[i+1,now] <= max[j+1,now];
当max[i+1,now] == max[j+1,now]时那么此时的状态就是无效状态。
令j=i-1 得到若 a[i]>a[i-1] ,则max[i+1,now] = max[i,now];
推广得,若存在 a[i] >=a[j] j<i那么a[i]为无效状态。
所以可以用优先队列进行优化 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
typedef  long long LL;

#define clr(x) memset((x),0,sizeof(x))
#define inf 0x3f3f3f3f

using namespace std;
const int maxn  = 100050;
int tree[maxn<<2];
LL a[maxn];
LL dp[maxn];
int q[maxn];
int l,r;
int n;
LL m;

int main()
{
  scanf("%d%I64d",&n,&m);
  LL cnt = 0;
  int k = 0;
  l = r = 0;
  dp[0] = 0;
  for(int i = 1;i<=n;i++)
  {
      scanf("%I64d",&a[i]);
      if(a[i]>m) {
          printf("-1\n");
          return 0;
      }
      cnt+=a[i];
      while(cnt>m) cnt-=a[++k];
      while(l<r&&a[i]>=a[q[r-1]]) r--;
      q[r++] = i;
      while(l<r&&q[l]<k) l++;
      dp[i] = 1e12;
      int tt = k;
      for(int j =l;j<r;j++){
  //     printf("%d ",q[j]);
          dp[i] = min(dp[i],dp[tt]+a[q[j]]);
          tt = q[j];
      }
  // printf("*%d ",dp[i]);
  // printf("\n");
  }

  printf("%I64d\n",dp[n]);
    return 0;
}