题目翻译

有一个长度为 $n(n\leq 500)$ 的序列,你可以砍 $m(0\leq m\lt n)$ 刀将其分为 $m+1$ 段,令每一段 $[l,r]$ 的贡献为 $\sum\limits_{i=l}^r \sum\limits_{j=i+1}^r a_i\times a_j$,一个序列的答案是每一段的贡献之和,求最小的答案。

题目思路

设 $f_{i,j}$ 表示前 $i$ 个位置割 $j$ 刀的最小答案。

转移是很显然的,枚举上一次断在哪里,$f_{i,j}\gets \min\limits_{k=0}^{i-1}f_{k,j-1}+calc(k+1,i)$。其中 $calc(l,r)$ 是 $[l,r]$ 的贡献。

不考虑 $calc$,转移是 $n^3$ 的,可以通过。但是这个 $calc$ 我们无法快速计算。

但是我们发现这个 $calc$ 被调用了 $n^3$ 次却至多只有 $n^2$ 个答案,所以我们考虑预处理 $calc(l,r)$ 记为 $g_{l,r}$。

$calc$ 有一个显然的 $\mathcal O(n^2)$ 降为 $\mathcal O(n)$ 的做法。根据乘法分配律,我们只关心这个数乘了多大的数,不关心具体乘了谁,记录前缀和,快速得到一个区间内的 $\sum a_i$ 即可 $\mathcal O(n)$ 求出单个 $calc$。

完整代码

#include <bits/stdc++.h>
using namespace std;
template <typename T>
void chkmn(T &x, T y) { x = min(x, y); }
int n, m;
int a[520];
int f[520][520];
int g[520][520];
int pre[520];
int calc(int l, int r)
{
    int ret = 0;
    for (int i = l; i <= r; i++)
        ret += a[i] * (pre[r] - pre[i]);
    return ret;
}
int main()
{
    memset(f, 0x3f, sizeof(f));
    cin >> n >> m;
    m++;
    for (int i = 1; i <= n; i++)
        cin >> a[i], pre[i] = pre[i - 1] + a[i];
    for (int l = 1; l <= n; l++)
        for (int r = l; r <= n; r++)
            g[l][r] = calc(l, r);
    memset(f[0], 0, sizeof(f[0]));
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= min(i, m); j++)
            for (int k = 0; k < i; k++)
                chkmn(f[i][j], f[k][j - 1] + g[k + 1][i]);
    cout << f[n][m] << endl;
    return 0;
}
最后修改:2024 年 02 月 25 日
v我50吃疯狂星期四