【二分答案】【单调队列优化dp】绿色通道

【题目描述】
高二数学《绿色通道》总共有 n 道题目要抄,编号1…n1…n,抄第 i 题要花 aiai分钟。小 Y 决定只用不超过 ttt 分钟抄这个,因此必然有空着的题。每道题要么不写,要么抄完,不能写一半。下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。

现在,小 Y 想知道他在这 t 分钟内写哪些题,才能够尽量减轻马老师的怒火。由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。
【输入描述】
第一行为两个整数 n,t
第二行为 n 个整数,依次为 a1,a2,…,an。
【输出描述】
输出一个整数,表示最长的空题段至少有多长。
【输入样例】
17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5
【输出样例】
3

【思路】

首先很显然,我们要让最长空题段最小,因此显然需要使用二分答案,再来进行判断。于是主要问题在于check函数。如此繁冗的状态,搜索?显然过不去。
由于这里具有无后效性和最优子结构,因此自然联想到dp,稍加思考 , 就能得到状态。我们用dp[i]表示到第i个题为止的总时间,因此最后只需比较dp[n-ans]~dp[n]和t的大小就能判断ans的合法性。因此状态转移方程就可以很轻松构思出来:
d p [ i ] = m i n ( d p [ j ] ) + a [ i ] ( j − a n s < = j < i ) dp[i]=min(dp[j])+a[i](j-ans<=j<i) dp[i]=min(dp[j])+a[i](jans<=j<i)
但是注意到题目的数据规模 大,因此我们需要进行优化,因此就用到了单调队列。我们用一个双端队列来维护min(dp[j]),这样就成为了O(1)查询。单调队列的思想很简单 ,对于一个dp[k]优于dp[j],k>j,那么显然dp[j]比dp[k]先失效,因此我们就可以不必再关注dp[j],而只保留dp[k]。如此维护,我们就得到了一个单调队列,队首的dp值最优,而靠近队尾的dp值较劣,但是靠近队首的元素总是先失效的。时间复杂度O(nlogn)。
代码如下:

//将进酒,杯莫停。
#include
#include
#include
#include
#include
#include
#include
#include
#define re register
using namespace std;
int n,m,c;
int a[50001];
struct node{
	int id,num;
}q[50001];
int dp[50001];
int t;
int head,tail;
bool check(int mid)
{
	memset(dp,127/3,sizeof(dp));
	head=tail=0;
	for(int re i=1;i<=n;i++)
	{
		dp[i]=q[head].num+a[i];
		while(head<=tail&&q[head].id<=i-mid-1)head++;//判断队首元素是否失效
		while(head<=tail&&dp[i]<q[tail].num)tail--;//用当前状态判断队尾元素是否失效
		q[++tail].id=i;
		q[tail].num=dp[i];
	
	}
	for(int i=n-mid;i<=n;i++)
	{
		if(dp[i]<=t)
			return 1;
	}
	return 0;
}
int main()
{
	scanf("%d%d",&n,&t);
	for(int re i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int l=0,r=n,mid;
	while(l+1<r)
	{
		mid=(l+r)>>1;
		if(check(mid))
			r=mid;
		else l=mid;
	}
	if(check(l))cout<<l;
	else cout<<r;
}

啊哈,又一道水题。

你可能感兴趣的:(【二分答案】【单调队列优化dp】绿色通道)