Codeforces 1239E Turtle

发现起点和终点不管怎么走都要经过,那就直接把最小的 \(2\) 个数分配到起点和终点即可。

对于第一行,有个挺容易想到的贪心策略,就是这行对应的 \(a_{1, i}\) 单增。
考虑若有 \(a_x > a_y, x < y\),那么如果这条路径从 \([1, x), [y, n]\) 下到第二行,\(a_x, a_y\) 交换位置不影响。
而若在 \([x, y)\) 换到第二行,交换 \(a_x, a_y\) 会使得这条路径长度中的 \(a_x\) 变为 \(a_y\),又因 \(a_y < a_x\),所以交换肯定更优。

类似的,第二行对应的 \(a_{2, i}\) 单减肯定更优。

\(f_i\) 为路径从从左往右第 \(i\) 个格子走下去对应的答案。
那就有 \(f_1 = a_{1, 1} + \sum\limits_{i = 1}^n a_{2, i}, f_i = f_{i - 1} + a_i - b_{i - 1}(i > 1)\)
能发现 \(f_i - f_{i - 1}(i > 1)\) 这个值是单增的,因为 \(a_i, -b_i\) 都单增。
那就说明 \(f_{1\sim n}\) 实际上成了一个下凸壳。
于是最大值只有可能在 \(f_1, f_n\) 取到。

又能发现 \(f_1, f_n\) 完全是两条独立的路径,无公共点且正好用完剩余 \(2n - 2\) 个值。
于是就可以考虑背包,\(f_{i, j, k}\) 为考虑前 \(i\) 个值选了 \(j\) 个值是否能凑出 \(k\)
可以用 bitset 优化转移。

方案可以根据背包状态来倒推。

时间复杂度 \(O(\frac{n^2\sum a}{w})\)

#include<bits/stdc++.h>
const int maxn = 25 + 2, maxm = maxn << 1, maxw = maxn * 50000 + 10;
std::bitset<maxw> f[maxm][maxn];
int a[maxm];
bool op[maxm];
int main() {
    int n, m; scanf("%d", &n), m = n << 1;
    for (int i = 1; i <= m; i++) scanf("%d", &a[i]);
    std::sort(a + 1, a + m + 1);
    f[2][1][0] = 1;
    int tot = 0;
    for (int i = 3; i <= m; tot += a[i++]) for (int j = 1; j <= n; j++)
        f[i][j] = f[i - 1][j] | (f[i - 1][j - 1] << a[i]);
    for (tot >>= 1; ! f[m][n][tot]; tot--);
    for (int i = m, j = n; j > 1; i--)
        tot >= a[i] && f[i - 1][j - 1][tot - a[i]] ? (op[i] = 1, j--, tot -= a[i]) : 0;
    printf("%d ", a[1]);
    for (int i = 3; i <= m; i++) op[i] && (printf("%d ", a[i]), 1);
    printf("\n");
    for (int i = m; i >= 3; i--) ! op[i] && (printf("%d ", a[i]), 1);
    printf("%d ", a[2]);
    return 0;
}

posted @ 2024-01-29 14:55  lhzawa  阅读(33)  评论(0)    收藏  举报