专栏文章

CF 2066F Curse

CF2066F题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miowy4l2
此快照首次捕获于
2025/12/03 02:30
3 个月前
此快照最后确认于
2025/12/03 02:30
3 个月前
查看原文
这也太难了,成官方题解复读机了/ll。
首先考虑一个例子:a=[1,1]a = [-1, -1]
那么接下来的操作一定是选择一个 1-1 进行替换,接下来分讨替换的数组的最大子段和:
  • >1> -1,则接下来只能替换这个数组中的最大子段和的位置。
  • =1= -1,则接下来可以替换这个数组中的最大子段和的位置,也可以替换 1-1。注意这个时候如果一个连续段既包含了左边的一部分又包含了 1-1 则其和一定 <1< -1,所以这是不可能的。
  • <1< -1,则接下来只能替换右边的 1-1
于是能够发现,我们可以增加一个分界符:[1,,1][-1, |, -1],其中每一步替换一定不会跨过分界符。
那么接下来就是要尝试去划分分界符了。
首先考虑找到最大子段和的段 [li,ri](1im)[l_i, r_i](1\le i\le m),此时称第 ii 段是有用的当且仅当不存在 1jm,ji1\le j\le m, j\not = i 满足 [lj,rj][li,ri][l_j, r_j]\subseteq [l_i, r_i],然后只保留下这些有用的段,对于中间的一些没有分进段内的元素继续递归下去分段。
这是因为若 [lj,rj][li,ri][l_j, r_j]\subseteq [l_i, r_i],那么把 [lj,rj][l_j, r_j] 替换成任意的数组 aa' 都可以通过把 [li,ri][l_i, r_i] 替换成 [li,lj)+a+(rj,ri][l_i, l_j) + a' + (r_j, r_i](这里的 ++ 指拼接)来实现。
因为有用的段不交,所以这个划分一定可以进行。
证明:考虑若 [li,ri][lj,rj][l_i, r_i]\cap [l_j, r_j]\not = \varnothing 且互不包含,不妨令 li<ljl_i < l_j,那么有 sum(li,rj)+sum(lj,ri)=sum(li,ri)+sum(lj,rj)sum(l_i, r_j) + sum(l_j, r_i) = sum(l_i, r_i) + sum(l_j, r_j),所以一定可以尝试替换为更大的 sumsum 的区间或者替换为长度更长的 [li,rj][l_i, r_j]
实现时可以直接考虑找出给定范围 [l,r][l, r] 中和最大且长度最长的区间, 然后往两侧递归。
于是此时其实就已经知道了所有分界线,也就可以划分出许多个段 [li,ri](1im)[l_i, r_i](1\le i\le m)(这里的名字跟上面的重了,但是因为上面的定义都不会用到了就这样吧)。
这些划分出来的段一定满足,对于这个段内的第一次替换操作一定是把整个段替换段。
紧接着,我们可以说明,进行的替换操作一定形如这样:
  • 选择一个值 xx,也就是主动划分了一个分界线。
  • 对于所有 sum(li,ri)<xsum(l_i, r_i) < x 的段全部保留。
  • 对于 sum(li,ri)xsum(l_i, r_i)\ge x 的段均可以进行替换,但是要保证至多一个段替换后最大子段和 >x> x
如果合法,那么这个过程是一定可以构造的,考虑先把所有 sum(li,ri)xsum(l_i, r_i)\ge x 的段都直接替换为 [x][x](即 xx 一个元素),然后先处理 sum(li,ri)xsum(l_i, r_i)\le x 的段,最后处理 sum(li,ri)>xsum(l_i, r_i) > x 的段。
且这样其实也说明了每一段至多也只会替换一次。
于是可以外层枚举 xx,内层设计一个 dp。
fi,j,0/1f_{i, j, 0 / 1} 表示目前在区间顺序(lil_i 升序)的第 ii 个区间,目前在匹配 bb 数组的第 jj 个位置,此时是否有选择最大子段和 >x> x 的段是否可行。
转移考虑进行分讨:
  • sum(li,ri)<xsum(l_i, r_i) < x,则无法匹配,直接枚举每个 jj 然后尝试接下来的 rili+1r_i - l_i + 1 个元素,复杂度是 O(i(rili+1)m)=O(nm)\mathcal{O}(\sum\limits_{i} (r_i - l_i + 1)m) = \mathcal{O}(nm) 的。
  • sum(li,ri)xsum(l_i, r_i)\ge x,则继续分讨接下来是选择替换成什么样的段:
    • 若选择替换成最大子段和 >x> x 的段,那么这一段可以匹配上 jj 为左端点的任意一个区间,即 fi+1,k+1,1(jkm)f_{i + 1, k + 1, 1}(j\le k\le m)
    • 若选择替换成最大子段和 x\le x 的段,考虑找到最大的 kk 满足 bb[j,k][j, k] 最大子段和 x\le x,那么就可以替换为左端点为 jj 右端点 k\le k 的任意区间,即 fi+1,h+1,(jhk)f_{i + 1, h + 1, *}(j\le h\le k)
    若直接转移复杂度是 O(nm2)\mathcal{O}(nm^2) 的。
    但是考虑到此时只关心合法性,若一个位置已经为 11 显然不需要重复赋值。
    所以对于第一类,肯定只需要找到最小的 jj 满足 fi,j,0f_{i, j, 0} 合法即可,复杂度为 O(nm)\mathcal{O}(nm)
    对于第二类,首先 kk 可以通过双指针解决,对于转移可以维护一个指针 r0/1r_{0 / 1} 表示 ro\le r_{o} 的类型为 oo 的右端点都不需要考虑了,均摊可以知道复杂度也为 O(nm)\mathcal{O}(nm)
于是求解合法性的过程都可以在 O(nm2)\mathcal{O}(nm^2) 的复杂度内做完。
对于构造,其实刚刚 dp 的过程就已经给出了对应的步骤,记录下来按照上文所述的构造方法构造即可。
我划分段时写的很暴力,复杂度是 O(n3+nm2)\mathcal{O}(n^3 + nm^2) 的。
代码也基本是参考的 std/ll。
C
#include <bits/stdc++.h>

constexpr int inf = 1e9;

constexpr int maxn = 500 + 5;

int n, a[maxn], suma[maxn];
int m, b[maxn], sumb[maxn];

std::vector<std::vector<int>> par;
std::vector<int> parsum;

void split(int l, int r) {
    if (l > r) return ;

    std::array<int, 4> mx = {-inf, 0, 0, 0};
    for (int i = l; i <= r; i++) {
        for (int j = i; j <= r; j++) {
            mx = std::max(mx, {suma[j] - suma[i - 1], j - i + 1, i, j});
        }
    }

    split(l, mx[2] - 1);
    par.push_back({});
    for (int i = mx[2]; i <= mx[3]; i++) {
        par.back().push_back(a[i]);
    }
    parsum.push_back(mx[0]);
    split(mx[3] + 1, r);
}

int mxb[maxn][maxn];

std::vector<std::tuple<int, int, std::vector<int>>> ans;

bool dp[maxn][maxn][2];
std::array<int, 2> lst[maxn][maxn][2];

inline bool check(int bound) {
    for (int i = 0; i <= par.size(); i++) {
        memset(dp[i], 0, sizeof(dp[i]));
    }
    dp[0][1][0] = true;

    for (int i = 0; i < par.size(); i++) {
        if (par[i] != std::vector<int>{inf}) {
            for (int j = 1; j + par[i].size() - 1 <= m; j++) {
                bool ok = true;
                for (int k = 0; ok && k < par[i].size(); k++) {
                    ok &= par[i][k] == b[j + k];
                }
                
                if (! ok) continue;
                for (int o : {0, 1}) {
                    if (dp[i][j][o]) {
                        dp[i + 1][j + par[i].size()][o] = true;
                        lst[i + 1][j + par[i].size()][o] = {j, o};
                    }
                }
            }
            continue;
        }

        int lstk[2] = {0, 0};
        for (int j = 1, r = 1; j <= m; j++) {
            if (r < j) r++;
            while (r <= m && mxb[j][r] <= bound) r++;
            
            if (! lstk[0] && dp[i][j][0]) {
                for (int k = j; k <= m; k++) {
                    dp[i + 1][k + 1][1] = true;
                    lst[i + 1][k + 1][1] = {j, 0};
                }
            }

            for (int o : {0, 1}) {
                if (dp[i][j][o]) {
                    lstk[o] = std::max(lstk[o], j);
                    while (lstk[o] < r) {
                        dp[i + 1][lstk[o] + 1][o] = true;
                        lst[i + 1][lstk[o] + 1][o] = {j, o};
                        lstk[o]++;
                    }
                }
            }
        }
    }

    if (! dp[par.size()][m + 1][1]) return false;

    int ci = -1, cl = -1, cr = -1;
    for (int i = par.size(), j = m + 1, o = 1; i > 0; i--) {
        auto [lstj, lsto] = lst[i][j][o];
        if (par[i - 1] == std::vector<int>{inf}) {
            if (o == lsto) {
                int id = 0;
                for (int k = 0; k < i - 1; k++) id += par[k].size();
                std::vector<int> now;
                for (int k = lstj; k < j; k++) now.push_back(b[k]);
                ans.emplace_back(id, id + par[i - 1].size() - 1, now);
                par[i - 1] = now;
            } else {
                ci = i - 1, cl = lstj, cr = j - 1;
            }
        }
        j = lstj, o = lsto;
    }

    int id = 0;
    for (int j = 0; j < ci; j++) id += par[j].size();
    std::vector<int> now;
    for (int j = cl; j <= cr; j++) now.push_back(b[j]);
    ans.emplace_back(id, id + par[ci].size() - 1, now);
    par[ci] = now;

    return true;
}

inline void solve() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= m; i++) scanf("%d", &b[i]);

    for (int i = 1; i <= n; i++) suma[i] = suma[i - 1] + a[i];
    for (int i = 1; i <= m; i++) sumb[i] = sumb[i - 1] + b[i];

    par.clear(), parsum.clear();
    split(1, n);

    for (int i = 1; i <= m; i++) mxb[i][i] = b[i];
    for (int l = m; l >= 1; l--) {
        for (int r = l + 1; r <= m; r++) {
            mxb[l][r] = std::max({mxb[l + 1][r], mxb[l][r - 1], sumb[r] - sumb[l - 1]});
        }
    }

    std::vector<int> order(par.size());
    std::iota(order.begin(), order.end(), 0);
    std::sort(order.begin(), order.end(), [&](int x, int y) {
        return parsum[x] > parsum[y];
    });

    ans.clear();
    for (int p = 0; p < order.size(); p++) {
        const int i = order[p];
        const int bound = parsum[i];
        
        int id = 0;
        for (int j = 0; j < i; j++) id += par[j].size();
        ans.emplace_back(id, id + par[i].size() - 1, std::vector<int>{inf});
        par[i] = {inf};

        if (p + 1 < order.size() && parsum[order[p + 1]] == bound) continue;

        if (check(bound)) {
            printf("%zu\n", ans.size());
            for (auto [l, r, vec] : ans) {
                printf("%d %d %zu\n", l + 1, r + 1, vec.size());
                for (int x : vec) printf("%d ", x == inf ? bound : x);
                puts("");
            }
            return puts(""), void();
        }
    }
    printf("-1\n\n");
}

int main() {
    int t; scanf("%d", &t);
    while (t--) solve();
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...