• POJ 3017 Cut the Sequence (单调队列优化DP)


    题意:

      给定含有n个元素的数列a,要求将其划分为若干个连续子序列,使得每个序列的元素之和小于等于m,问最小化所有序列中的最大元素之和为多少?(n<=105。例:n=8, m=17,8个数分别为2 2 2 | 8 1 8 |1 2,答案为12。)

    思路:

      想明白一个队列+一个set就能完美解决这个问题?

      首先DP的转移式子是:dp[i]=min( dp[j] +max[j+1, i]  ),且sum[i]-sum[j]<=m,j为枚举的断开处。暴力寻找一个合适的j的复杂度为O(n2)。那么问题就在于如何寻找这个合适的j。

      先假设j的范围是(low, i],而max是随着j的增大单调不减的,则max[k,i]>=max[p,i]且k<p是肯定的。那么如果出现max[k,i]=max[p,i]且k<p的话,j=k明显更佳,因为dp[k]<=dp[p]是肯定的!那么如果出现max[k,i]>max[p,i]且k<p的话,取哪个就难说了,也是因为dp[k]<=dp[p] 。

      根据转移式子知道,j肯定是不跟i同组的,这是根据dp[j]的定义来决定的。那么j有可能的取值为low,或者为k(a[k]>a[i]),那么单调队列就形成了,队列中保存下标,表示所有a[k]>a[i]且low<=k<=i。

      但是这队列有什么用?值val可能等于队列中任一元素u,而val=dp[u]+max[u+1,i]是没有什么规律的。直接将所有val装进set中,最小的set.begin()就是我们要找的答案了。如果维护单调队列时需要删除怎么办?直接将算出来的val在set中删除。

      注意:如果队列为空,即a[i]是a[low,i]中的最大值,那么最多只能取j=low。如果low一旦改变了,队头元素也有可能改变喔~因为队头算出来的val可能是根据更小的low算出来的。由于多个val相同的情况是存在的,所以用multiset。

     1 //#include <bits/stdc++.h>
     2 #include <iostream>
     3 #include <cstdio>
     4 #include <cstring>
     5 #include <cmath>
     6 #include <set>
     7 #include <deque>
     8 #include <map>
     9 #include <algorithm>
    10 #include <vector>
    11 #include <iostream>
    12 #define pii pair<int,int>
    13 #define back que[rear-1]
    14 #define INF 0x7f7f7f7f
    15 #define LL long long
    16 #define ULL unsigned long long
    17 using namespace std;
    18 const double PI  = acos(-1.0);
    19 const int N=100100;
    20 LL sum[N], has[N], dp[N];
    21 int a[N], que[N], top, rear, low;
    22 multiset<LL> sett;
    23 LL cal(int n,LL m)
    24 {
    25     for(int i=1; i<=n; i++)
    26     {
    27         if(a[i]>m)  return -1;
    28         while( sum[i]-sum[low]>m )  low++;
    29 
    30         //过期的只可能在top处
    31         while( top<rear && que[top]<=low )
    32             sett.erase( has[top++] );
    33 
    34         if(top<rear)
    35         {
    36              //队头的val有可能会改变,因为下限low提高了
    37             sett.erase( has[top] );
    38             has[top]=dp[low]+a[ que[top] ];
    39             sett.insert( has[top] );
    40         }
    41         //插入队列先
    42         while(top<rear && a[i]>a[ que[rear-1] ] )
    43             sett.erase( has[--rear] );
    44 
    45         que[rear]=i;    //只记下标
    46         LL val=0;
    47         if(top^rear)    val=dp[ que[rear-1] ]+a[i];
    48         else            val=dp[low]+a[i]; //top=rear时,a[i]就是最大的
    49         sett.insert( val );
    50         has[rear++]=val;
    51         dp[i]=*sett.begin();    //取最小
    52     }
    53     return dp[n];
    54 }
    55 
    56 int main()
    57 {
    58     //freopen("input.txt","r",stdin);
    59     int n;LL m;
    60     scanf("%d%lld", &n, &m);
    61     for(int i=1; i<=n; i++) //机器
    62     {
    63         scanf("%d",&a[i]);
    64         sum[i]=sum[i-1]+a[i];
    65     }
    66     printf("%lld
    ", cal(n,m));
    67     return 0;
    68 }
    AC代码
  • 相关阅读:
    swiper插件的使用demo
    可能要用的东西
    VIDEO
    vue上传图片加水印
    图片 base64 file blob 之间相互的转化
    vant 上传图片加水印
    JS 随机排序算法
    ubuntu16.04 下apache 搭建站点
    Unity常用目录对应的Android && iOS平台地址
    IOS 官方实现单例模式
  • 原文地址:https://www.cnblogs.com/xcw0754/p/4864935.html
Copyright © 2020-2023  润新知