题意理解
题目问"最长的空题段至少有多长",等价于问:
是否存在一种抄题方案,使得总时间 ≤ t,且任意连续空题段长度 ≤ x?求这个x的最小值。
解题思路
a. 二分思想
如果空题间隔x过小,可能总时间会超过t;如果x过大总时间达不到t,但不是满足题意条件的最小值。 总时间随着x的增加,可能是不上升的,可以想到二分。我们写一个二分,初始化l=-1(不可取到),r=n(可取)。令mid=(l+r)/2,写个check函数,如果check(mid)返回True,说明x超过了t,这时l=mid;否则r=mid。
b.check函数实现------单调队列优化DP
根据题意,在最大空题间隔为x的情况下,如果最小的空题方案还大于x,那就返回True;否则返回False。所以我们要求在最大空题间隔为x的情况下,能使总时间最短的空题方案。前i题里面,我们记抄了第i题的最短总时间为dpi。
那么对于i(1<=i<=n),我们可以让ai+min{dpj}(i-x-1<=j<=i-1)。这里就可以考虑单调队列。在实现上,前x+1个数有可能i-x-1会下标越界,要特判。我们也可以虚拟一个第0题,耗时0分钟,这个最先入队,就可以不用写特判。
最后,我们的答案会在n-x,n之间找最小值。我们也可以虚拟一个第n+1题耗时0分钟,必做。通过下面的代码,可以把最小值转移到dpn+1上。最后,返回dpn+1>=t。
核心代码
cpp
bool check(int x){
deque<int> dq;
dq.push_back(0);
for(int i=1;i<=n+1;i++){
while(dq.size()&&i-dq.front()>x+1) dq.pop_front();
int ft=dq.front();
dp[i]=dp[ft]+a[i];
while(dq.size()&&dp[i]<=dp[dq.back()]) dq.pop_back();
dq.push_back(i);
}
return dp[n+1]>=t;
}
AC代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5e4+5;
int a[maxn],s,n,t,dp[maxn];
bool check(int x){
deque<int> dq;
dq.push_back(0);
for(int i=1;i<=n+1;i++){
while(dq.size()&&i-dq.front()>x+1) dq.pop_front();
int ft=dq.front();
dp[i]=dp[ft]+a[i];
while(dq.size()&&dp[i]<=dp[dq.back()]) dq.pop_back();
dq.push_back(i);
}
return dp[n+1]>=t;
}
int main(){
cin>>n>>t;
for(int i=1;i<=n;i++){
cin>>a[i];
s+=a[i];
}
int l=-1,r=n;
while(l+1<r){
int mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
cout<<r;
return 0;
}