Codeforces1420E. Battle Lemmings 题解 动态规划

题目链接:https://codeforces.com/contest/1420/problem/E

题目大意:

给你一个长度为 (n)(01)序列,每一次操作你可以交换相邻的两个元素。
定义序列的 保护值protection )为“序列中一对数值为 (0) 的数,且这对数之间夹着至少一个 (1)”的对数。
请计算当最多执行 (i) 次交换操作((i in [0, frac{n(n-1)}{2}]))的情况下序列保护值的最大值各是多少。

问题分析

对于给定的一个 (01) 序列 (A),我们定义另一种状态 (f(A))(A) 的另外一种表述方式。

(f(A)) 仍然是一个集合,但是它表示的含义如下:

  • 其第一个元素表示 (A) 中第一个 (1) 前面的 (0) 的数量;
  • 其第二个元素表示 (A) 中介于第一个 (1) 和第二个 (1) 之间的 (0) 的数量;
  • ……
  • 其最后一个元素表示在最后一个 (1) 后面的 (0) 的数量。

举个例子:(f(011000101100) = {1, 0, 3, 1, 0, 2}),在这个例子中:

  • 在第一个 (1) 前面有 (1)(0)
  • 在第一个 (1) 和第二个 (1) 之间有 (0)(0)
  • 在第二个 (1) 和第三个 (1) 之间有 (3)(0)
  • ……
  • 在最后一个 (1) 后面有 (2)(0)

可以发现,将原始的 (A) 转换为 (f(A)) 并不困难。

现在,如果我们交换序列 (A) 中两个相邻的不同的数,(f(A)) 也会发生相应的变化。也就是说,一旦交换两个相邻的数,(f(A)) 中的一对相邻的数就会发生变化(一个加一,一个减一)。

让我们定义如下一个任务,定义两个序列 (A = { a_1, a_2, cdots, a_k }) 以及 (B = {b_1, b_2, cdots, b_k})。我们需要计算将序列 (A) 变成序列 (B) 的最少交换次数(明显地,(A)中所有元素之和等于(B)中所有元素之和)。

为了解决这个问题,我们假设在第(i)个元素后面设置一个“屏障”。于是序列(A)变成了两部分:左边部分(第(1)(i)个元素)和右边部分(第(i+1)(n)个元素)。为了让序列(A)(B)的左右部分相同,你需要在第(i)(i+1)个元素之间进行 (g_{A, B}(i) = left|sum_{j=1}^{i} a_i - sum_{j=1}^i b_i ight|) 次交换操作。而总的交换次数为

[sum_{i=1}^{n-1} g_{A,B}(i) ]

对于一个序列 (A) 来说,假设已知它的转换序列 (f(A) = {f_1, f_2, cdots, f_k}), 则我们可以通过如下公式求得序列的 保护值protection(p(A))

[p(A) = sum_{1 le i < j le k} f_icdot f_j = frac 12 left( sum_{1 le i, j le k} f_icdot f_j - sum_{i=1}^k f_i^2 ight) = frac 12 left( left(sum_{i=1}^k f_i ight)^2 - sum_{i=1}^k f_i^2 ight). ]

因为 (sum_{i=1}^k f_i) 已知,所以我们的目的是最小化 (sum_{i=1}^k f_i^2)

我们定义 一轮交换 的含义是:第 (i) 轮我们交换了 (f_i)(f_{i+1}) 若干次,然后最终的 (f_i) 确定下来了 —— 这样算一轮。(也就是第 (i) 轮交换,我们需要将 (f(A)) 中的第 (i) 个元素和第 (i+1) 个元素交换若干轮,交换结束时,第 (i) 个元素就确定了,这一轮交换才算完成)

接下来我们尝试找到序列 (f(A) = {f_1, f_2, cdots, f_k}) 在不超过 (k) 轮交换下的最佳序列。

我们需要定义一个状态 (dp_{i, s, k}) ,它表示的含义是:

我们已经确定了 (f(A)) 的前 (i) 个元素,并且进行了 (k) 交换的情况下,并且 (sum_{i=1}^k f_i) 等于 (s) 的情况下,对应的 (sum_{i=1}^k f_i^2) 的最小值。

为了得到答案我们需要先计算得到 (dp_{i, c_0, k}),其中 (c_0) 表示原始序列中 (0) 的数量。

并且为了计算 (dp_{i, s, k}) 我们需要去遍历最终 (f_{i+1}) 可能的情况 —— 这里假设它会变为 (h)

于是,可以得到状态转移方程如下:

[dp_{i+1, s + h, k + left|z_i - (s + h) ight|} ext{min=} dp_{i, s, k} + h^2. ]

这里, (z_i) 表示初始序列 (f(A))(sum_{j=1}^i f_i) 的值。

总体时间复杂度为 (O(n^5)) ,但是代码实现中常数比较小,所以最终的时间复杂度能达到 (O(frac{n^5}{27}))

示例代码:

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 105;
int n, a[maxn], b[maxn], k, p[maxn], dp[maxn][maxn][maxn*(maxn-1)/2];
void chk_min(int &x, int y) {
    x = min(x, y);
}
int main() {
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> a[i];
    a[n] = 1;
    for (int i = 0; i <= n; i ++) {
        if (a[i] == 0) b[k] ++;
        else k ++;
    }
    partial_sum(b, b+k, p);
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0][0] = 0;
    int l = p[k-1];
    for (int i = 0; i < k; i ++) {
        for (int j = 0; j <= l; j ++) {
            for (int s = 0; s <= n*(n-1)/2; s ++) {
                if (dp[i][j][s] == INF) continue;
                for (int q = j; q <= l; q ++) {
                    chk_min(dp[i+1][q][s + abs(q - p[i])], dp[i][j][s] + (q-j)*(q-j));
                }
            }
        }
    }
    int mn = INF;
    for (int s = 0; s <= n*(n-1)/2; s ++) {
        chk_min(mn, dp[k][l][s]);
        int val = l * l - mn;
        assert(val % 2 == 0);
        cout << val / 2 << " ";
    }
    return 0;
}

参考资料:https://codeforces.com/blog/entry/82978

原文地址:https://www.cnblogs.com/quanjun/p/13731374.html