标签:索引 set name namespace 指南 插入 return lld typedef
题目链接:https://www.acwing.com/video/864/
给定一个长度为n的序列,问将这个序列分成连续的若干段,每段不超过M的情况下,每段的最大值之和最小是多少?
如果数据范围比较小的话就可以不进行任何优化,用dp[i]表示将[1,i]分成若干段满足条件的情况下每段最大值最小的情况,那么dp[j]就容易从dp[i]进行转移,但是转移中存在一个max函数,不是单调的。
所以需要对决策集合进行考虑。
复杂度较高的情况:
第一次筛选:找到最小的j使得[j+1,i]之间的和是不超过M的。
第二次筛选:在左边决策变量j对应的值不超过右边序列的最大值的情况下尽量向左延伸。因为这样右侧的最大值不会发生变化,但是左侧的最大值和不会变大,所以总的最大值之和不会变大。容易知道这种情况下A[j]就是[j,i]序列的最大值。所以我们需要维护的就是满足这样一个性质的序列,如果有一个决策点j1和j2,满足j1<j2而且a[j1]<=a[j2],那么显然可以再向左延伸而不会使结果变的更差。
由于计算的是f[j]+max{A[k]}(j+1<=k<=i),不满足单调性,因此上述每一个决策j都需要进行计算,从中找到最小的就是f[i]。
通过单调队列就能维护这样一个决策变量j的可能取值,而f[i]最初的取值是最左端j对应的f[j]+q[l],其中q[l]是单调队列中的首元,也就是[j+1,i]中最大值对应的下标。
容易知道,对每个q[k],下一个数q[k+1]就是q[k]之后最大的数对应的下标。
证明:假设q[k]右侧的A的最大值对应的索引为s,如果A[s]<=A[q[k+1]],条件满足,如果s>q[k+1],根据单调队列的性质,应该去除q[k+1]点,并且加入s,但是队列当前已经满足性质,故s点一定是q[k+1]。
证毕。
该算法保证每个决策在队列中只被扫描一遍,但最坏的情况下可以达到O(n^2)。通过堆优化能保持O(nlogn)时间复杂度。堆用来在对数时间内查找最小值,要满足插入和删除操作,其中删除操作需要谨慎处理。
代码1:POJ能过3017
#include<iostream> #include<cstdio> #include<algorithm> #include<set> using namespace std; const int N = 100010; typedef long long ll; int n; ll m; ll a[N]; ll q[N]; ll f[N]; int main(){ scanf("%d%lld",&n,&m); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); if(a[i]>m){ puts("-1"); return 0; } } int l=0,r=0; ll sum = 0; for(int i=1,j=0;i<=n;i++){ sum+=a[i]; while(sum>m)sum-=a[++j];//初步筛选所有可能的j while(l<=r && q[l]<=j)l++; while(l<=r && a[q[r]]<=a[i])r--; q[++r]=i;//二次筛选所有可能的j f[i]=f[j]+a[q[l]];//第二种情况或者第一种情况中的边界 for(int k=l;k<r;k++) f[i]=min(f[i],f[q[k]]+a[q[k+1]]); } cout<<f[n]<<endl; return 0; }
《算法竞赛进阶指南》0x59单调队列优化DP AcWing 299裁剪序列
标签:索引 set name namespace 指南 插入 return lld typedef
原文地址:https://www.cnblogs.com/randy-lo/p/13425053.html