题目描述 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代码如下
1.
/*
使用优先队列优化
*/
#include
#include
#include
#include
#include
#include
#include
#include
#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)
{
return a.mincost>b.mincost;
}
};
int n,T;
int a[N];
int dp[N];
int check(int x)
{
dp[0]=0;
priority_queue 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;
}
2.
/*
使用单调队列
对于单调队列,我们这样子来定义:
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
#include
#include
#include
#include
#include
#include
#include
#include
#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].numx-1) head++;//如果队首元素下标已经不在区间内,则将其出队,即head++
dp[i]=q[head].mincost+a[i];//此时队首元素即为区间[i-x-1,i-1]内的最小值
while(headq[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
#include
#include
#include
#include
#include
#include
#include
#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;
}