ABC374 EF 题解

ABC374 E 题解

E - Sensor Optimization Dilemma 2

题目链接:AT | 洛谷

容易发现答案可以二分。于是我们把问题转化为了判定性问题:给定目标 x,能否在花费不超过预算的情况下,使得每个过程的产量都不低于 x

进一步,由于各个过程的生产互不相关,所以我们要尽可能让每个过程的费用尽可能小,这样一定是最优的。对于每个过程分别考虑,就是要问:

给定两种机器,第一种机器的产量为 a,价格为 p;第二种机器的产量为 b,价格为 q。要使总产量达到 x,如何让花费尽可能少?

(其实我从这里开始就想不出来了)

一种显然的暴力是枚举某一种机器的数量——例如第一种,从 0 枚举到 xa,同时可以 O(1) 计算出所需的第二种机器的数量。这样算出的答案显然一定正确,但枚举的次数是 O(x/a),当 x 很大且 a 很小时,无法接受。

想到正解或许需要一点灵感。考虑这个事实:如果要生产 ab 个零件,有两种方案:购买 b 个第一种机器,花费 bp;或 a 个第二种机器,花费 aq。可能还有别的方案,但可以别的证明一定不会更优。(从“性价比”的角度来考虑。)

从这个事实,可以推出:在最优方案中,两种机器的产量不可能同时达到 ab如果两种机器的产量都达到了 ab,总可以把较贵的 ab 产量交换给另一种机器,使得费用更低。因此,以下两种情况不可能同时发生:

  1. 第一种机器的数量达到 b
  2. 第二种机器的数量达到 a

这实际上就是“两种机器的产量不可能同时达到 ab”的另一种表述方法。

有了这个结论之后,就可以分别枚举两种机器的数量:第一种机器从 0 枚举到 b1,第二种机器从 0 枚举到 a1。根据上述结论,这样就可以保证枚举到最优策略。设 ab 的值域为 V,我们就在 O(log(XV)NV) 的时间内通过了此题。

参考代码:

auto check = [&](const i64 x) -> bool
{
    i64 tot = 0;
    for(int i = 0; i < n; i++)
    {
        i64 a = A[i], b = B[i], p = P[i], q = Q[i], xx = x, add = 0x3f3f3f3f3f3f3f3f;

        for(int j = 0; j < b; j++)
        {
            i64 tmp = j * p + ceil(max(0ll, xx - j * a), b) * q;
            add = min(add, tmp);
        }
        for(int j = 0; j < a; j++)
        {
            i64 tmp = j * q + ceil(max(0ll, xx - j * b), a) * p;
            add = min(add, tmp);
        }

        tot += add;
        if(tot > lim) return false;
    }
    return true;
};

int L = 0, R = 1e9, ans = -1;
while(L <= R)
{
    int mid = (L + R) >> 1;
    if(check(mid)) L = mid + 1, ans = mid;
    else R = mid - 1;
}

提交记录

F - Shipping

题目链接:AT | 洛谷

这是一道用结论优化 dp 转移的题目。下面逐步观察性质,得到正解。

首先进行一步简单的转化:题目要求最小化 i=1N(SiTi)=i=1NSii=1NTi,其中 i=1NTi 是定值,我们只需最小化 i=1NSi 即可。下面就简单地称 Si 的和为“不满意度”。

性质 1:每次交付的订单必定是连续的。

这点可以通过调整法来证明:假设在某个方案中,某次交付的订单不是连续的,把它调整成连续的,不满意度一定不会变大。

有了这个性质之后,我们就有地方下手了。可以想到 dp:设 f(i,j) 表示对于前 i 条订单,最后一次交付时间为第 j 天时,最小的不满意度之和。转移时,枚举一次交付的订单数量 k 和上一次交付的时间 d

f(i,j)=min1kmin(i,K){mindjX{f(ik,d)}+k×j}

这样,状态数是 O(NTN),单次转移的时间复杂度是 O(KX),时空复杂度都爆炸了。

我们可以在转移过程上优化:确定 k 时,不必枚举 f(ik,d) 来寻找其最小值。每次计算完 f(i,j) 后,令 f(i,j)min(f(i,j),f(i,j1))(这实际上让 f(i,j) 变为了 f(i,) 的前缀最小值),这样 f(iK,jX) 就是我们要找的最小值,于是单次转移的时间复杂度降到了 O(K)

但即使是这样还远远不够。还能进一步优化吗?我们注意到当前的最大问题在于 TN 很大,能不能缩小可能作为交付时间的日期范围呢?

下面给出第二个性质:

性质 2:可能作为交付时间的日期必然满足如下形式:Ti+kX,其中 1iN0kN

也就是说,要么在某个订单刚被下达的那一天交付,要么在某个订单下达时间经过若干个 X 天后交付。

可以这么理解:首先第一次交付的时间必定是某个 Ti。因为是第一次交付,所以不必考虑两次交付直接相隔时间的限制,所以到某个 Ti 天就可以直接交付,继续等待是没有意义的。之后的交付时间,要么同样在某个 Ti 天交付,要么考虑上一次交付与这一次交付之间的时间限制,必须在上一次交付的基础上再过 X 天。根据第一次交付的时间必定是某个 Ti 这个基础,可以推出所有的交付时间都形如 Ti+kX。最后,为什么 kN?显然,每次交付至少会交付一个订单,这样最多交付 N 次。

(这只是感性理解,严谨证明我也不会。)

于是,我们可以先预处理出 D 表示所有可能作为交付天数的集合。转移方程和原来类似:f(i,j) 表示对于前 i 条订单,最后一次交付时间为第 Dj 天时,最小的不满意度之和。(只是把“第 j 天”改为了“第 Dj 天”。)当然我们还是要求出前缀最小值。

时间复杂度分析:显然 |D|=O(N2),因此状态数为 O(N3),单次转移的时间复杂度为 O(K),总时间复杂度为 O(N3K)。由于时限很宽松,可以通过。(实际上由于常数很小,这个做法跑得飞快)

实现细节:

  • 计算 f(i,) 时,用一个指针来维护最后一个小于 DjX 的天数。
  • Dj<Ti 时不能转移。此时可以设 f(i,j)=+

参考代码:

f.resize(n + 1, vector<i64>(tot + 1, INF));
f[0][0] = 0;
for(int i = 1; i <= n; i++)
{
    int lst = 0;
    for(int j = 1; j <= tot; j++)
    {
        if(d[j] < t[i]) continue;
        while(lst < tot && d[lst + 1] <= d[j] - x) lst++;
        for(int k = 1; k <= min(i, m); k++) // 枚举一次发货的货物数量
            f[i][j] = min(f[i][j], f[i - k][lst] + k * d[j]);
        f[i][j] = min(f[i][j], f[i][j - 1]); // 前缀最小值
    }
}

i64 ans = *min_element(f[n].begin() + 1, f[n].end()) - accumulate(t.begin(), t.end(), 0ll);
cout << ans << endl;

提交记录

posted @   DengStar  阅读(146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签
点击右上角即可分享
微信分享提示