AcWing 1090. 绿色通道

AcWing 1090. 绿色通道

一、题目描述

高二数学《绿色通道》总共有 n 道题目要抄,编号 1,2,,n,抄第 i 题要花 ai 分钟。

Y 决定只用 不超过 t 分钟 抄这个,因此必然有空着的题。

每道题要么不写,要么抄完,不能写一半。

下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。

这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。

现在,小 Y 想知道他在这 t 分钟内写哪些题,才能够尽量减轻马老师的怒火。

由于小 Y 很聪明,你只要 告诉他最长的空题段至少有多长 就可以了,不需输出方案。

输入格式
第一行为两个整数 n,t

第二行为 n 个整数,依次为 a1,a2,,an

输出格式
输出一个整数,表示最长的空题段至少有多长。

数据范围
0<n5×104,0<ai3000,0<t108

输入样例:

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

输出样例

3

二、二分+DP

t<=108,好BT的数据上限!这就是在提示我们,不能选择O(N)的算法,那样基本上凉了!需要有一个O(logN)的算法: 二分

目标

  • t 分钟内写完作业
  • 尽量减轻马老师的怒火
  • 找出最长空白长度

分析

  • 空白区越大,用时越少
  • 空白区越小,用时越大

如果我们选择 二分 的思路:

通过二分,找出一个当前的尝试的区间空白最大值limit,

如果当前枚举到的区间空白最大值limit,

  • 可以在t分钟内完成,那么让limit再小一点试试,如果小一点的也可以在t分钟内完成,马老师的怒火会更小一点,惹毛了马老师,后果严重。
  • 不能在t分钟内完成,那么让limit再大一点试试,这样也许就能写完了,马老师肯定会更生气,但这不是最重要的,最重要的是我得能在t分钟内写完。

当这样不断的二分查找到最后时,就达到了一种 状态

  • 能在t分钟内完成
  • 尽量让空白区间最大值越小
  • 在能完成作业的情况下,尽量多的写一些,马老师的愤怒值最小

2、细节问题
如果i选中,那么它前面最远的可以是哪个?此处与烽火传递不太一样,那个是 在连续 m 个烽火台中至少要有一个发出信号,翻译一下就是连续m个中必须有一个,也就是空白区最多是m1个,此题是空白区是limit个,比那个大1

Code

#include <bits/stdc++.h>

using namespace std;
const int N = 50010;
const int INF = 0x3f3f3f3f;

int n, m;
int w[N];
int f[N]; // 已经完成前i个,并且第i个被选中 情况下抄袭时间最小值

// 暴力+DP 通过了 8/12个数据
bool check(int limit) {
    // 注意这个初始化,我吃了这个亏~
    memset(f, 0x3f, sizeof f);
    f[0] = 0;

    for (int i = 1; i <= n; i++)                             // 计算前i道题,填表填数据
        for (int j = max(0, i - limit - 1); j <= i - 1; j++) // 第i题肯定是选中的,那么它前面的是哪个位置选中呢?
            f[i] = min(f[i], f[j] + w[i]);

    // 最后[n-limit,n]之间必然得有一题答了吧,这些都是合法解
    int res = INF;
    for (int i = n - limit; i <= n; i++) res = min(res, f[i]);
    // 最小值比m小的话,那么就是有可行解
    return res <= m;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i];

    // 最长的空题段∈[1,n]

    // 二分(求最长的最小)
    int l = 0, r = n; // 空段区间长度0~n都有可能,注意此处是带0的,因为可能老师要求每题必答,对吧,你以为隔一题空一题老师能忍,其实人家忍不了!
    // 事实也证明了这一点,我尝试修改为 int l=1,r=n;会挂掉一个测试点! 
    /*
    空白时段的长度越长,花费的时间越少
    空白时段的长度越短,花费的时间越长
    */
    while (l < r) {
        int mid = (l + r) >> 1; // 一定要搞清楚mid的含义:模拟的最长空题段长度
        if (check(mid))         // 如果这个空白长度可以使得整体时间<=m,那么空白长度可以再小一点,这是个弯,好好想想
            r = mid;
        else
            l = mid + 1; // 如果现在的长度不能使得时间够用,只能是长度再长一点
    }
    printf("%d\n", l);
    return 0;
}

三、单调队列优化

上面的思路是对的,因为可以正常通过12中的7个测试点,但其它的TLE了,需要优化:

因为状态转移方程定义的方式,决定了i是必选的,那么w[i]确定 要忍受的代价,整体代价要想最小,只能是前面的代价最小!

f[i]的所有前序可选状态是:i左侧limit+1个范围,也就是要找出这个范围内的min(f[j]),j[ilimit1,i1]

指定区间内的最小值问题,可以使用单调队列来优化:

Q:答案在哪里?

需要在nnlimit之间去枚举找出最小值做为答案。

举个栗子秒懂:
n=10,limit=3,那么如果7被选中,那么8,9,10是不用写的。

#include <bits/stdc++.h>

using namespace std;
const int N = 50010;
const int INF = 0x3f3f3f3f;

int n, m;
int w[N];
int f[N];
int q[N], hh, tt;

bool check(int limit) {
    hh = 0, tt = 0, q[0] = 0;
    for (int i = 1; i <= n; i++) {
        // 窗口的长度是limit
        while (hh <= tt && q[hh] < i - limit - 1) hh++;

        // 直接计算
        f[i] = f[q[hh]] + w[i];

        // i入队列
        while (hh <= tt && f[q[tt]] >= f[i]) tt--;
        q[++tt] = i;
    }
    // 答案可能存在于 n,n-2,...n-m中,枚举一下求最少值即可
    int res = INF;
    for (int i = n - limit; i <= n; i++) res = min(res, f[i]);
    // 最小值比m小的话,那么就是有可行解,我才不管是不是有其它的值也比m小不小!
    return res <= m;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i];
    // 二分(求最长的最小),就是碰一下区间的长度,大了就变小,小了就变大
    int l = 0, r = n; // 空段区间长度0~n都有可能
    // 当我们允许的空白时段的长度越长,那么花费的时间越少
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << l << endl;
    return 0;
}
posted @   糖豆爸爸  阅读(109)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
历史上的今天:
2018-01-18 迪杰斯特拉算法
Live2D
点击右上角即可分享
微信分享提示