codevs 3342 绿色通道(二分+dp+优先队列or单调队列)好题

题目描述 Description
《思远高考绿色通道》(Green Passage, GP)是唐山一中常用的练习册之一,其题量之大深受lsz等许多oiers的痛恨,其中又以数学绿色通道为最。2007年某月某日,soon-if (数学课代表),又一次宣布收这本作业,而lsz还一点也没有写……

高二数学《绿色通道》总共有n道题目要写(其实是抄),编号1…n,抄每道题所花时间不一样,抄第i题要花a[i]分钟。由于lsz还要准备NOIP,显然不能成天写绿色通道。lsz决定只用不超过t分钟时间抄这个,因此必然有空着的题。每道题要么不写,要么抄完,不能写一半。一段连续的空题称为一个空题段,它的长度就是所包含的题目数。这样应付自然会引起马老师的愤怒。马老师发怒的程度(简称发怒度)等于最长的空题段长度。

现在,lsz想知道他在这t分钟内写哪些题,才能够尽量降低马老师的发怒度。由于lsz很聪明,你只要告诉他发怒度的数值就可以了,不需输出方案。(快乐融化:那么lsz怎么不自己写程序?lsz:我还在抄别的科目的作业……)

输入描述 Input Description
第一行为两个整数n,t,代表共有n道题目,t分钟时间。

以下一行,为n个整数,依次为a[1], a[2],… a[n],意义如上所述。

输出描述 Output Description
仅一行,一个整数w,为最低的发怒度。

样例输入 Sample Input
17 11

6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5

样例输出 Sample Output
3

数据范围及提示 Data Size & Hint
60%数据 n<=2000

100%数据 0 < n <=50000,0 < a[i] <=3000,0< t<=100000000

思路:首先我们可以很容易的想到对发怒值进行二分,假设发怒值为x,check的时候,我们可以考虑用dp来写,dp[i]表示前i个题,我们选择做第i题时所需花费的最少时间。令dp[0]=0,那么状态转移方程就是dp[i]=min(dp[j])+a[i],i-1>=j>=i-1-x && j>=0(因为i到j之间我们最多可以空x道题不写),如果只想到这里用暴力来找区间内最小的dp[j]还是会tle的。因为我们需要找的是[i-1-x,i-1]这个区间内dp[j]的最小值,所以我们可以考虑用优先队列或者单调队列来优化。思路1用优先队列:取队首元素u,如果i-1-u.num<=x,说明[i-1-x,i-1]这个区间dp[j]的最小值即为u.mincost,否则将队首元素其移出队列,接着找下一个。思路2用单调队列优化:维护一个单调递增队列,由于每个元素最多入队一次,出队一次,所以这部分的时间复杂度就降到了O(n),比用优先队列时间复杂度更低,具体实现看代码。

ac代码如下

/*
 使用优先队列优化
*/
#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cstring>
#define LL long long int 
using namespace std;
const int N=1e6+5;
struct node
{
	int num;
	int mincost;
	node(int _num=0,int _mincost=0):num(_num),mincost(_mincost){}
	friend bool operator <(node a,node b)//运算符重载,意思是当a.mincost>b.mincost为真时node a<node b
	{
		return a.mincost>b.mincost;
	}
};
int n,T;
int a[N];
int dp[N];
int check(int x)
{
    dp[0]=0;
    priority_queue<node> que;
    que.push(node(0,0));
    for(int i=1;i<=n;i++)
    {
    	while(!que.empty())
    	{
    		node u=que.top();
    		if(i-u.num-1<=x)
    		{
    			dp[i]=a[i]+u.mincost;
    			que.push(node(i,dp[i]));
    			break;
			}
			else 
			{
				que.pop();
			}
		}
		if(n-i<=x)//到了末尾我们最多可以空x道题不写,所以如果此时dp[i]小于等于所给的时间T时,说明发怒度x满足条件,check返回1
		{
			if(dp[i]<=T)
			{
	        return 1;
	        }
	    }
	}
	return 0;
}

int main()
{
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	int l=-1;
	int r=n;
	while(r-l>1)
	{
		int mid=(r+l)/2;
		if(check(mid))
		r=mid;
		else 
		l=mid;
	}
	printf("%d\n",r);
	return 0;
}
/*
使用单调队列
对于单调队列,我们这样子来定义: 
1、维护区间最值 
2、去除冗杂状态 ,区间中的两个元素a[i],a[j](假设现在再求最小值) 若 j>i且a[j]<=a[i] ,a[j]比a[i]还小而且还在后面(目前a[j]留在队列肯定比a[i]有用,所以你就可以把a[i]出队,即tail--) 
3、保持队列单调,最小值是单调递增序列,最小值反之 
4、最优选择在队首

大致过程: 
1、维护队首(head++) 
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态,tail--) 
*/
#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<vector>
#include<cstring>
#define LL long long int 
using namespace std;
const int N=1e6+5;

struct node
{
	int num;
	int mincost;
	node(int _num=0,int _mincost=0):num(_num),mincost(_mincost){}
};
int n,T;
int a[N];
int dp[N];
node q[N];
int check(int x)
{
	    int head=0,tail=1;//head指向队首元素,tail-1指向末尾元素
	    q[0]=node(0,0);
		for(int i=1;i<=n;i++)
		{
			while(q[head].num<i-x-1) head++;//如果队首元素下标已经不在区间内,则将其出队,即head++
			dp[i]=q[head].mincost+a[i];//此时队首元素即为区间[i-x-1,i-1]内的最小值
 			
 			while(head<tail&&dp[i]<q[tail-1].mincost) tail--;//将队尾的那些值比dp[i]大的元素出队
 			q[tail++]=node(i,dp[i]);//将dp[i]入队
		 } 
	    
		for(int i=n-x-1;i<=n;i++)
		if(dp[i]<=T)
		return 1;
		
		else 
		return 0;
}

int main()
{
    scanf("%d%d",&n,&T);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    int l=-1;
    int r=n;
    while(r-l>1)
    {
        int mid=(r+l)/2;
        if(check(mid))
        r=mid;
        else 
        l=mid;
    }
    printf("%d\n",r);
    return 0;
}

tle 代码如下

#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cstring>
#define LL long long int 
using namespace std;
const int N=1e6+5;
#define INF 0x3f3f3f3f
int n,T;
int a[N];
int dp[N];
int check(int x)
{
    dp[0]=0;
    for(int i=1;i<=n;i++)
    {
    	int minn=INF;
    	for(int j=i-1;j>=0&&j>=i-1-x;j--)//这部分用优先队列或者单调队列优化后就可以过了
    	{
    		minn=min(minn,dp[j]); 
		}
		dp[i]=minn+a[i];
		if(n-i<=x)
		{
			if(dp[i]<=T)
			{

	        return 1;

	        }
	    }
	}
	return 0;
}

int main()
{
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	int l=-1;
	int r=n;
	while(r-l>1)
	{
		int mid=(r+l)/2;
		if(check(mid))
		r=mid;
		else 
		l=mid;
	}
	printf("%d\n",r);
	return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值