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\right|\) 次交换操作。而总的交换次数为

\[\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_i\cdot f_j = \frac 12 \left( \sum_{1 \le i, j \le k} f_i\cdot f_j - \sum_{i=1}^k f_i^2 \right) = \frac 12 \left( \left(\sum_{i=1}^k f_i \right)^2 - \sum_{i=1}^k f_i^2 \right). \]

因为 \(\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)\right|} \text{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

posted @ 2020-09-25 17:20  quanjun  阅读(268)  评论(0编辑  收藏  举报