CF939F Cutlet题解

前言

蒟蒻没实力独立想出这题,看了很多题解,也借鉴了大佬的思路,如果看着和其他的比较像,求勿喷,谢谢。

题意

有一块肉排,要煎 \(n\) 秒,一共有 \(2n\) 秒可以用来煎肉排。给出了 \(k\) 个时间段,\(l_i\)\(r_i\),在这些时间段里可以翻转肉排。要求找出最小的翻转次数。

规定:下面是正在烤的面,上面反之。

暴力解法

大神可以直接跳过

分析

感觉两面的地位是平等的,所以考虑下面就好,又是找最小翻转次数,我们考虑 DP。

定义 \(dp[i][j]\) 表示 \(i\) 时刻,初始时候的下面烤了 \(j\) 时间的最小翻转次数,此时的下面就是开始的时候烤的面。

转移如下:

  • 不翻转:\(dp[i][j] = dp[i-1][j-1]\)
  • \(i\) 在可翻转区间:\(dp[i][j] = \min(dp[i-1][j-1], dp[k][j-1] + 2)\)(其中 \((k,i)\) 不烤,翻转两次)

然后我们发现时间复杂度是 \(O(n^3)\) 的,肯定不行,看看怎么优化。发现 \(k\) 的枚举是不必要的,其实我们可以记录对于每一个 \(j\)\([1,i-1]\) 里最优的 \(k\) 是哪一个,甚至不需要 \(k\) 的值,只需要知道最小的贡献就可以了,注意只有 \(i+1\) 时刻是可翻转的,\(i\) 时刻才可以被记录。

代码

void solve() {
    memset(d, 0x3f, sizeof d);
    memset(dp[p^1], 0x3f, sizeof dp[p^1]);
    dp[p^1][0] = 0;
    for (int i = 1; i <= n << 1; i++) {
        memset(dp[p], 0x3f, sizeof dp[p]);
        while (w <= k && r[w] < i && l[w+1] <= i) w++;
        for (int j = n; j >= 1; j--) {
            if (check(i, w)) {
                dp[p][j] = min(dp[p^1][j-1], d[j-1] + 2);
            } else {
                dp[p][j] = dp[p^1][j-1];
            }
            if (check(i+1, w + (r[w] < i+1))) {
                d[j] = min(d[j], dp[p][j]);
            }
            if (j == n && (i == 2*n || check(i, w))) {
                ans = min(ans, dp[p][j] + (i != n*2));
            }
        }
        p ^= 1;
    }
}

虽然时间复杂度已经变成 \(O(n^2)\) 了,但还是 T 了。
似乎已经没有优化的空间了,这个状态定义有一个巨大的 Bug,总会有一个从 \(dp[i-1][j-1]\) 转移过来的情况,至少要 \(O(n)\),这很不优秀。这时候我们发现,非翻转区间不会进行操作,没有烤的那个面是不受影响的,所以我们考虑上面。

正解

好的性质:

  1. 如果一直不翻转,上面被烤的时长是不变的。
  2. 在一个区间内,翻转次数不会超过两次。简要证明:如果超过了两次,我们不妨把中间翻转处连接起来,这样少烤的时间是相等的。

分析

定义dp[i][j]表示时刻i,没有在烤的面被烤了j时间的最小翻转次数。那么最终的答案只需取min(dp[i][n])即可。
转移如下:

  • dp[i][j]=dp[i-1][j] 不翻转的情况
  • dp[i][j]=min(dp[i][j],dp[k][k-j]+1) 翻转一次,k在可翻转区间
  • dp[i][j]=min(dp[i][j],dp[k][j-(p-k)]+2) 翻转两次,k<p且都在可翻转区间

由于在可翻转区间外的时刻对答案没有贡献,所以我们处理完上一个区间可以直接跳到下一个区间,我们只需要从上一个区间的右端点进行转移就行。这个方法是从其他大佬那里学习的,个人的理解是因为对于每一个j,dp[i][j]应该是单调不增的,从转移可以得到,并且对于跨区见的两次翻转操作可以忽略忽略掉在前一个区间的翻转,因为这会包含在之前的转移里。

所以我们不妨改变一下dp数组的定义,dp[i][j]表示r[i]时刻,上面烤了j时间的最小翻转次数。所以我们只需要考虑同一个区间的翻转操作即可。

转移如下:

  • dp[i][j]=dp[i-1][j]
  • dp[i][j]=min(dp[i][j],dp[i-1][j+l[i]-k]+1) l[i]<=k<=r[i]
  • dp[i][j]=min(dp[i][j],dp[i-1][j-(p-q)]+2) l[i]<=q<p<=r[i]

时间复杂度为\(O(kn^3)\),这似乎并不优秀,让我们接着优化。

单调队列优化

翻转一次:发现可以转移的区间是[j-(r[i]-l[i]),j],随着j的移动区间长度是固定的,所以我们可以用单调队列优化。
翻转两次:翻转的位置对答案是没有影响的,所以我们直接按照(p-q)的范围进行转移就行,0<len<=r[i]-l[i]
这样复杂度就变成\(O(nk)\)了,正确。另外,空间可以滚动数组优化。

posted @ 2025-05-29 13:29  wxw_zl  阅读(21)  评论(0)    收藏  举报