【C++心路历程25】课堂讲义【dp加单调队列】

【问题描述】

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

  现在,xxx想知道他在这 t 分钟内写哪些题,才能够尽量降低x老师的发怒度。由于xxx很聪明,你只要告诉他发怒度的数值就可以了,不需输出方案。

【输入格式】

  第一行为两个整数n,t,代表共有n道题目,t分钟时间。
  以下一行,为n个整数,依次为a[1], a[2],… a[n],意义如上所述。

【输出格式】

  仅一行,一个整数w,为最低的发怒度。

【输入样例】

17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5

【输出样例】

3

【样例解释】

  分别写第4,6,10,14题,共用时 2+3+3+3=11 分钟。空题段:1-3(长为3),5-5(长为1),7-9(长为3),11-13(长为3),15-17(长为3 )。   所以发怒度为3。可以证明,此数据中不存在使得发怒度<=2的作法。

【数据范围】

30%数据 n<=2000,0

void solve()
{
    int x=1,y=n,ans=n;
    while(x<=y)
    {
        int m=(x+y)/2;
        run(m);
        long long tmp=//空题段不超过m的情况下,完成题目所需的最小时间;
        if(tmp>t)//完成不了 
            x=m+1;
        else
        {
            y=m-1;
            ans=m;
        }   
    }
    printf("%d",ans);
}

现在的关键是处理
tmp,即空题段不超过m的情况下,完成题目所需的最小时间;
这里可以采用dp
方程:
//f(i)表示在前i道题中必选择第i道题做 && 最小空题段数不超过m时 的最小时间
//f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i]

朴素的dp:

//solve30
//f(i)表示在前i道题中必选择第i道题做 && 最小空题段数不超过m时 的最小时间 
//f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const long long inf=40000000000000005;
int n,t;
int a[100005];
long long f[100005];
void run(int m)
{//f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] 
    f[0]=0;
    for(int i=1;i<=m;i++)
        f[i]=a[i];
    for(int i=m+1;i<=n+1;i++)
        f[i]=inf;   
    long long t;
    for(int i=1;i<=n+1;i++)
    {
        t=inf;
        for(int j=i-m-1;j<=i-1;j++)
        {
            t=min(t,f[j]);
        }
        f[i]=t+a[i];
    }
}
void solve()
{
    int x=1,y=n,ans=n;
    while(x<=y)
    {
        int m=(x+y)/2;
        run(m);
        long long tmp=f[n+1];//空题段不超过m的情况下,完成题目所需的最小时间;
        if(tmp>t)//完成不了 
            x=m+1;
        else
        {
            y=m-1;
            ans=m;
        }   
    }
    printf("%d",ans);
}
int main()
{
//  freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&t);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    a[n+1]=0;   

    solve();    
    return 0;
}

时间复杂度分析:O(n * n *log2 n)
显然不能通过全部数据。
分析方程
f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i]
实际上实在窗口长度为m时找一个f(j)的最小值,因此可以用单调序列优化。

//solve100

void run(int m)
{//f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] 
    f[0]=0;
    for(int i=1;i<=m;i++)
        f[i]=a[i];
    for(int i=1;i<=n+1;i++)
        f[i]=inf;   
    long long t;
    rear=0,front=0;
    q[rear++]=(data){0,0};
    for(int i=1;i<=n+1;i++)
    {
        while(front!=rear && i-q[front].id-1>m) front++;
        f[i]=q[front].v+a[i];
        while(front!=rear && q[rear-1].v>f[i])  rear--;
        q[rear++]=(data){f[i],i};
    }
} 

时间复杂度:O(n*log2 n *log2 n)(每个数据进队一次出队一次)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值