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代码如下

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;
}

你可能感兴趣的:(dp,优先队列,二分+三分+分治)