AcWing 1090.绿色通道
原题链接
题目解析
摘自《动态规划 · 二、数据结构优化DP · 单调队列优化》 。
可以看到,我们希望空的题尽可能的分散,然而我们好像无法从正面直接枚举得到答案(一种一种组合的列举显然是不可能的),那我们便可以换个思路从答案开始思考:
设最长的空题段长度为k ,那么显然方案满足这一点,且在最优安排下满足时间小于等于t ,而空题段长度大于k 时显然不满足最优(马老师会很生气)可以继续向k 靠近,小于k 时总时间会大于t ,不符合题意。
于是我们发现,以正确答案k 为界,将一个[0,N] 的区间分成了两半,一半为合法非最优,一半不合法。这启示我们使用二分,逼近正确答案k 。当二分的
k 固定下来,问题变成了“在一个数列空出若干个连续长度不超过k 的数之后数列的和是否小于t ”,我们显然很难从“紧接着”转移状态,即转移过程非链状,而是跳跃的,所以我们设状态f[i] 表示在第i 道题并不空第i 题下所需的最少时间,那么为了保证空题段合法,那么状态转移方程为:f[i]=\min\limits_{i-k-1\leq j<i}\{f[j]+w_i\}=\min\limits_{i-k-1\leq j<i}\{f[j]\}+w_i
废话不多说,这里主要把单调队列的用法细节详细讲解。输入不必多说,从二分开始。
-
二分
首先,这个空的题目数量不应该为负数,所以左边界为 check()函数返回false时给l加一或给r减一。
成品如下:
int l=0,r=n;
while(l<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
-
单调队列优化DP
首先我们要搞清楚一件事,那就是对于f[i]来讲,他是由f[j]转移过来的并且j满足i-k-1<=j<i,所以单调队列存的是区间
那么我们在更新一个f[]时,就应当先把其前f[]的最小值用单调队列求出来,并且,在一开始,f[2]该由谁转移过来呢?显然不一定是 f[1] ,所以在一开始还要垫一个
那么最后的答案呢?一定是 f[n] 吗?显然最后一道并不一定要做,所以答案应当为
成品如下:
bool check(int k){
hh=0,tt=-1;
q[++tt]=0;//先垫一个0
f[1]=a[1];//f[1]无需多虑
for(int i=2;i<=n;i++){
while(hh<=tt && q[hh]<i-k-1) hh++;//边界为i-k-1
while(hh<=tt && f[q[tt]]>=f[i-1]) tt--;//升序单调队列
q[++tt]=i-1;//队列中存下标
f[i]=f[q[hh]]+a[i];
}
for(int i=n-k;i<=n;i++)
if(f[i]<=t) return true;
return false;
}
当然可以更简洁:
bool check(int k){
hh=0,tt=0;
for(int i=1;i<=n;i++){
while(hh<=tt && q[hh]<i-k-1) hh++;
f[i]=f[q[hh]]+a[i];
while(hh<=tt && f[q[tt]]>=f[i]) tt--;
q[++tt]=i;
}
for(int i=n-k;i<=n;i++)
if(f[i]<=t) return true;
return false;
}