• LianYunGang OI Camp: Contest #2


    LianYunGang OI Camp: Contest #2

    本次比赛选择了一些背包动态规划问题。

    总体难度适中,预期人均通过三题以上。

    A: Knapsack 1

    Link: https://atcoder.jp/contests/dp/tasks/dp_d?lang=en

    简单的 01背包问题。题解及代码略。

    B: Match Matching

    Link: https://atcoder.jp/contests/abc118/tasks/abc118_d?lang=en

    如果把火柴棍拼出的数字视作物品,可以发现本题是一个完全背包模型。物品的重量就是所需火柴棍的个数,物品的价值则需要综合考量数字的位数与大小。

    首先考虑答案的位数。一种自然的做法是,用状态 (f_i) 表示能由 (i) 根火柴拼出最大数字的位数,显然有:

    [large f_i = max_{k = A_1,dots,A_m} { f_i,~f_{i-w_k} + 1 } ]

    其中 (w_k) 是拼出数字 (k) 对应所需的火柴棍根数。

    求出最长位数之后,可以反过来寻找状态转移的路径(答案的每一个数位),注意优先取数值大的数字即可。复杂度是 (O(MN + Mlog M)),证明留给读者。

    #include <bits/stdc++.h>
    
    const int w[] = {0, 2, 5, 5, 4, 5, 6, 3, 7, 6};
    int n, m, a[10], dp[16384];
    
    int main() {
        std::cin >> n >> m;
        for (int i = 0; i != m; ++i) {
            std::cin >> a[i];
        }
        std::sort(a, a + m, std::greater<int>());
    
        memset(dp, -1, sizeof(dp));
        dp[0] = 0;
        for (int i = 0; i != m; ++i) {
            for (int j = w[a[i]]; j <= n; ++j) {
                if (dp[j - w[a[i]]] == -1) continue;
                dp[j] = std::max(dp[j], dp[j - w[a[i]]] + 1);
            }
        }
    
        while (n) {
            for (int i = 0; i != m; ++i) {
                if (n < w[a[i]]) continue;
                if (dp[n] - dp[n - w[a[i]]] == 1) {
                    n -= w[a[i]];
                    std::cout << a[i];
                    break;
                }
            }
        }
        std::cout << std::endl;
        return 0;
    }
    

    C: Baby Ehab Partitions Again

    Link: https://codeforces.com/problemset/problem/1516/C

    先考虑一个子问题:如何检查某一组数 (a_1, dots, a_k) 是否能被分为和相等的两部分?

    当然应该检查 (S_k = sum_{i=1}^k a_i) 的奇偶性。若 (S) 为奇数则必定无解,否则问题转化为:在 (a_1, dots, a_k) 中选出一些数,使它们的和为 (frac{S_k}{2})

    这是一个01背包模型。我们用状态 (f_{i,j}) 表示前 (i) 个数是否能表示出数字 (j),很自然地有:

    [large f_{i, j} = f_{i-1, j} or f_{i-1,j-a_i} ]

    这个式子可以滚动,甚至可以 bitset 加速,但这不是很重要。

    接着我们考虑原问题。原数列天然不能被分成两部分的不论,我们考虑 (S_n) 为偶数且数列可以被分成相等两部分的情况。

    若:

    1. 存在一个奇的 (a_i),那么将其移除即可。
    2. 所有 (a_i) 均为偶数,容易发现我们令所有 (a_i = a_i / 2) 不会影响答案。于是我们可以不断进行除以二的操作,直到数列中出现奇数为止——或者可以一步到位,令 (a_i = a_i / gcd(a_1,dots,a_n)) 即可。
    #include <bits/stdc++.h>
    
    int n, com, sum, ans, a[128];
    std::bitset<262144> f;
    
    bool check() {
        if (sum & 1)  return 0;
        f[0] = true;
        for (int i = 0; i != n; ++i) {
            f |= (f << a[i]);
        }
        return f[sum / 2];
    }
    
    int gcd(int a, int b) {
        return (b == 0)? a: gcd(b, a % b);
    }
    
    int main() {
        std::cin >> n;
        for (int i = 0; i != n; ++i) {
            std::cin >> a[i];
            com = gcd(a[i], com);
        }
        for (int i = 0; i != n; ++i) {
            a[i] /= com;
            sum += a[i];
            if (a[i] & 1) ans = i;
        }
        
        if (check()) std::cout << 1 << '
    ' << ans + 1 << std::endl;
        else         std::cout << 0 << std::endl;
    
        return 0;
    }
    

    D: Bottles

    Link: https://codeforces.com/problemset/problem/730/J

    我们课堂上讲过的原题。做法见之前分发的讲义。

    E: Chef Monocarp

    Link: https://codeforces.com/problemset/problem/1437/C

    一个显要的观察是,对 (t_i) 升序排列之后依次取出的答案一定更优。证明很简单:假设 (t_i < t_j, T_i > T_j),那么有不等式 (|t_i - T_i| + |t_j - T_j| > |t_i - T_j|+|t_j - T_i|) 成立。所以一定是升序更优。

    接着考虑在排序后的 (t_i) 上dp,设状态 (f_{i, j}) 表示前 (i) 道菜都已取出,且当前时间 (T = j) 时顾客的最小不满度,显然有转移:

    [large f_{i, j} = min(f_{i, j -1},~f_{i-1, j-1} + |t_i - j|) ]

    #include <bits/stdc++.h>
    
    int n, t[256], f[256][512];
    
    int main() {
        int T; scanf("%d", &T);
        while (T--) {
            scanf("%d", &n);
            for (int i = 0; i != n; ++i) {
                scanf("%d", &t[i]);
                t[i]--;
            }
            std::sort(t, t + n);
    
            memset(f, 0x7f, sizeof(f));
            f[0][0] = 0;
            for (int i = 0; i <= n; ++i) {
                for (int j = 0, siz = 2 * n - 1; j != siz; ++j) {
                    f[i][j + 1] = std::min(f[i][j + 1], f[i][j]);
                    if (i >= n) continue;
                    f[i + 1][j + 1] = std::min(f[i + 1][j + 1], f[i][j] + std::abs(t[i] - j));
                    
                }
            }
            printf("%d
    ", f[n][2 * n - 1]);
        }
        return 0;
    }
    

    F: Unmerge

    Link: https://codeforces.com/problemset/problem/1381/B

    首先观察到一个结论:某段连续的 (a_i, a_{i + 1}, dots, a_k, ~a_i = max_{i leq j leq k} a_j) 一定不会被拆分到两个不同子序列中去,否则就不能保证归并后 (a_i) 的位置。

    于是我们可以考虑将给定的 (a_1, dots, a_{2n}) 分成一些满足上述性质的连续子段 (t_1,dots,t_k),再考虑这些子段的组合。
    以样例 3 2 6 1 5 7 8 4 为例,我们可以将其分为 [3 2][6 1 5][7][8 4],并可以发现 [3 2 | 8 4], [6 1 5 | 7] 可归并成原序列。

    容易进一步观察到,无论这些子段如何归属到两个子序列中,归并结果都总是原序列。我们只需要保证题设的两个子序列长度相等即可。于是问题转化为给定一些数字 (|t_1|,dots,|t_k|),询问是否能从中选出一些数字,使它们的和为 (n)。这是一个容易dp的问题。

    考虑用状态 (f_{i, j}) 表示在前 (i) 个数中选出一些,它们的和为 (j) 的可行性。显然有转移:

    [large f_{i, j} = f_{i - 1, j} or f_{i - 1, j - |t_i|} ]

    容易压缩一维,虽然不必要。

    #include <bits/stdc++.h>
    
    const int N = 4096;
    int n, a[N], t[N];
    bool f[N];
    
    int main() {
        int T; scanf("%d", &T);
        while (T--) {
            scanf("%d", &n);
            for (int i = 0; i != 2 * n; ++i) {
                scanf("%d", a + i);
            }
    
            int siz = 0, ind = 0, cnt = 0, head = a[0];
            while (ind != 2 * n) {
                if (a[ind] <= head) {
                    ++cnt, ++ind;
                } else {
                    t[siz++] = cnt;
                    cnt = 0, head = a[ind];
                }
            }
            if (cnt) {
                t[siz++] = cnt;
            }
    
            memset(f, false, sizeof(f));
            f[0] = true;
    
            for (int i = 0; i != siz; ++i) {
                for (int j = n; j >= t[i]; --j) {
                    f[j] |= f[j - t[i]];
                }
            }
            if (f[n]) puts("YES");
            else puts("NO");
        }
        return 0;
    }
    
  • 相关阅读:
    父容器的flowover:hidden 必须配合父容器的宽高height width才能生效
    css使 同一行内的 文字和图片 垂直居中对齐?
    vim查找替换
    要让div中的float不会自动显示到下一行来?
    如何理解clear的css属性?
    div+css使多行文字垂直居中?
    VMnet1和V8
    unslider的用法详解
    json的中括号和大括号的使用?
    如何合理定价,定价定天下,必须要学会这个哦!
  • 原文地址:https://www.cnblogs.com/Shimarin/p/15024885.html
Copyright © 2020-2023  润新知