zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    微软一站式示例代码库(中文版)20110924版本, 新添加ASP.NET, Windows Base, Silverlight, WinForm等20个Sample
    Expression Blend实例中文教程系列文章汇总
    【分享】Silverlight 探秘系列课程汇总(下载)
    【分享】分享几个不错的富文本编辑器插件
    推荐 10 个优秀的 HTML5 编码工具
    SQLite数据库操作类
    每天写出好代码的5个建议
    ASP.NET中26个常用性能优化方法
    ConnectString中enlist设置的含义
    使用VS2008打开VS2010的解决方案
  • 原文地址:https://www.cnblogs.com/quanjun/p/13731374.html
Copyright © 2011-2022 走看看