CF2027D The Endspeaker (Hard Version) 题解

题面

给你一个长度为 \(n\) 的数组 \(a\) 和一个长度为 \(m\) 的数组 \(b\) (所有 \(1 \le i < m\) 满足 \(b_i > b_{i+1}\) )。最初, \(k\) 的值是 \(1\) 。您的目标是通过重复执行这两种操作中的一种,使数组 \(a\) 为空:

  • \(1\) - 如果 \(k\) 的值小于 \(m\) ,并且数组 \(a\) 不是空的,那么可以将 \(k\) 的值增加 \(1\) 。这不会产生任何费用。
  • \(2\) - 从数组 \(a\) 中移除一个非空的前缀,使得其总和不超过 \(b_k\) 。需要花费 \(m - k\)

你需要最小化操作的总成本,使数组 \(a\) 为空。如果无法通过任何操作序列实现,则输出 \(-1\) 。否则,输出操作的最小总成本,以及产生这个最小成本的操作序列的数目,取模为 \(10^9 + 7\)

如果在任何一步中选择了不同的操作类型,或在任何一步中删除的前缀大小不同,则认为两个操作序列是不同的。

题解

D1(Easy Version)

题目只要求求出最小总成本,所以考虑 \(dp\),我们根据 \(b_i\) 的单调性,容易发现选择时对 \(b_i\) 按顺序对成本进行松弛即可,唯一需要确定的是状态的来源。

考虑什么时候转移是合法的,必然是一个区间和小于 \(b_i\) 时合法,如果一个区间和小于 \(b_i\),有一个较小的区间被这个区间所包含,那么一定是不优的,因此我们尽可能的将区间扩大,找到最大满足区间和小于 \(b_i\) 的区间,可以枚举左端点或右端点做双指针转移,对每个 \(b_i\) 转移一次即可。

D2(Hard Version)

在 D1 的条件下,我们需要求出转移的方案数,那么对于我们转移的方式应该枚举右端点,这样才能确定每一次转移后的方案数。

对于一个段 \(l\) 转移到 \(r\),若对于某个 \(b_i\) 能使 \(l\) 转移到 \(r\)

  • \(dp_r - dp_l > m - i\) 时,将 \(dp_l\) 的方案数赋值到 \(dp_r\) 上。

  • \(dp_r - dp_l < m - i\) 时,我们跳过这样的 \(l\)

  • \(dp_r - dp_l = m - i\) 时,我们将连续的 \(l\) 方案数求和,转移到 \(dp_r\) 上。

因此,我们可以记录一个 \(maxx\) 代表当前区间段 \(dp_l = dp_{maxx}\) 可以转移到 \(dp_r\) 的区间段的右端点,如果正在考虑的 \(l\) 小于 \(maxx\),那么可以对这个区间段内满足 \(dp_r - dp_l = m - i\) 的部分求和,可以证明 \(l\) 是单调递增的,如果 \(l\) 大于 \(maxx\),更新 \(maxx\),方案数替换为下一个合法的区间,令 \(maxx = l\) 继续转移。

容易发现,上述操作和双指针无异,时间复杂度 \(O(nm)\)

参考代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, mod = 1e9 + 7;
int n, m;
ll a[N], b[N];

void solve()
{
    cin >> n >> m;
    vector<ll> pre(n + 1);
    for (int i = 1; i <= n; i ++ ) cin >> a[i], pre[i] = pre[i - 1] + a[i];
    for (int i = 1; i <= m; i ++ ) cin >> b[i];
    vector<ll> dp(n + 1, 1e18), f(n + 1);
    dp[0] = 0, f[0] = 1;
    for (int i = 1; i <= m; i ++ )
    {
        ll sum = 0;
        for (int l = 0, r = 1, maxx = 0; r <= n; r ++ )
        {
            while (pre[r] - pre[l] > b[i]) (sum -= f[l]) %= mod, l ++ ;
            if (maxx < l) maxx = l, sum = 0;
            while (maxx < r && dp[maxx] == dp[l]) (sum += f[maxx]) %= mod, maxx ++ ;
            if (l < r)
            {
                if (dp[r] > dp[l] + m - i) dp[r] = dp[l] + m - i, f[r] = sum;
                else if (dp[r] == dp[l] + m - i) (f[r] += sum) %= mod;
            }
        }
    }
    if (dp[n] == 1e18) cout << "-1\n";
    else cout << dp[n] << ' ' << (f[n] + mod) % mod << '\n';
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T = 1;
    cin >> T;
    while (T -- ) solve();
    return 0;
}
posted @ 2024-10-27 15:53  YipChip  阅读(7)  评论(0编辑  收藏  举报