ABC374 EF 题解

ABC374 E 题解

E - Sensor Optimization Dilemma 2

题目链接:AT | 洛谷

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

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

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

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

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

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

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

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

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

有了这个结论之后,就可以分别枚举两种机器的数量:第一种机器从 \(0\) 枚举到 \(b - 1\),第二种机器从 \(0\) 枚举到 \(a - 1\)。根据上述结论,这样就可以保证枚举到最优策略。设 \(a\)\(b\) 的值域为 \(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 转移的题目。下面逐步观察性质,得到正解。

首先进行一步简单的转化:题目要求最小化 \(\sum_{i = 1}^{N} (S_i - T_i) = \sum_{i = 1}^{N} S_i - \sum_{i = 1}^{N} T_i\),其中 \(\sum_{i = 1}^{N} T_i\) 是定值,我们只需最小化 \(\sum_{i = 1}^{N} S_i\) 即可。下面就简单地称 \(S_i\) 的和为“不满意度”。

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

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

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

\[f(i, j) = \min_{1 \le k \le \min(i, K)} \{\min_{d \le j - X} \{f(i - k, d)\} + k \times j\} \]

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

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

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

下面给出第二个性质:

性质 2:可能作为交付时间的日期必然满足如下形式:\(T_i + kX\),其中 \(1 \le i \le N\)\(0 \le k \le N\)

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

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

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

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

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

实现细节:

  • 计算 \(f(i, \ast)\) 时,用一个指针来维护最后一个小于 \(D_j - X\) 的天数。
  • \(D_j < T_i\) 时不能转移。此时可以设 \(f(i, j) = +\infty\)

参考代码:

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 @ 2024-10-06 17:20  DengStar  阅读(133)  评论(0编辑  收藏  举报