题解:洛谷 AT_abc466_e Range Flip

【题目来源】

洛谷:AT_abc466_e [ABC466E] Range Flip

【题目描述】

\(N\) 张卡片排成一列,卡片的编号分别为 \(1,2,\ldots,N\)

\(i\) 张卡片的正面写有整数 \(A_i\),背面写有整数 \(B_i\)。初始时,所有卡片均为正面朝上。

你最多可以进行 \(K\) 次以下操作:

  • 选择满足 \(1\le l\le r\le N\) 的整数 \(l,r\)
  • 对于所有满足 \(l\le i\le r\) 的整数 \(i\),将第 \(i\) 张卡片翻面。

翻面是指:将操作前朝下的一面翻到朝上。

请你求出所有操作结束后,各张卡片朝上的一面所写整数之和的最大值。

【输入】

输入从标准输入读入,格式如下:

\(N\) \(K\) \(A_1\) \(B_1\) \(A_2\) \(B_2\) \(\vdots\) \(A_N\) \(B_N\)

【输出】

输出能够得到的最大总和。

【输入样例】

7 2
2 1
6 9
3 5
9 2
4 8
7 4
5 6

【输出样例】

45

【核心思想】

  1. 问题分析:给定 \(N\) 张卡片,每张卡片正面为 \(A_i\)、背面为 \(B_i\),初始全部正面朝上。最多进行 \(K\) 次区间翻转操作(每次选择区间 \([l,r]\) 将区间内所有卡片翻面),求最终朝上面数字之和的最大值。这是一个线性动态规划问题,核心在于将区间翻转操作转化为卡片状态的连续变化,利用"翻转次数的奇偶性"和"区间操作的连续性"设计状态。

  2. 算法选择

    • 一维线性 DP:设 \(dp[i][j][0/1]\) 表示前 \(i\) 张卡片、使用了 \(j\) 次操作、第 \(i\) 张卡片正面朝上(\(0\))或背面朝上(\(1\))时的最大和
    • 状态转移分析:利用区间翻转的连续性——第 \(i\) 张卡片的状态取决于第 \(i-1\) 张卡片是否处于同一个翻转区间内
  3. 关键步骤

    • 初始化\(dp[i][0][0] = dp[i-1][0][0] + A_i\)\(0\) 次操作时所有卡片正面朝上
    • 状态转移(遍历 \(i\)\(1\)\(N\)\(j\)\(1\)\(\min(i, K)\)):
      • \(i\) 张正面朝上\(dp[i][j][0]\)):
        • 不翻转第 \(i\) 张:第 \(i-1\) 张可以是正面或背面,取最大值 \(\max(dp[i-1][j][0], dp[i-1][j][1]) + A_i\)
      • \(i\) 张背面朝上\(dp[i][j][1]\)):
        • 延续翻转区间:第 \(i-1\) 张也是背面朝上,\(dp[i-1][j][1] + B_i\)
        • 开始新翻转区间:第 \(i-1\) 张正面朝上,消耗一次操作,\(dp[i-1][j-1][0] + B_i\)
        • 取两者最大值
    • 答案提取\(ans = \max_{0 \leq j \leq K}(dp[N][j][0], dp[N][j][1])\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot K)\),双层循环,每层状态转移为 \(O(1)\)
    • 空间复杂度:\(O(N \cdot K)\),三维 DP 数组(可优化为 \(O(K)\) 滚动数组)
  5. 线性 DP 与区间翻转的核心思想

    • 翻转的连续性建模:区间翻转操作意味着被翻转的卡片形成一个或多个连续区间。\(dp[i][j][1]\) 中"延续翻转区间"对应第 \(i\) 张与第 \(i-1\) 张在同一个翻转区间内,"开始新翻转区间"对应第 \(i\) 张是某个新区间的起点
    • 操作次数的精确计数:每次"开始新翻转区间"消耗一次操作次数,而"延续翻转区间"不消耗。这确保了操作次数 \(j\) 的正确统计
    • 状态压缩:只需记录第 \(i\) 张卡片的朝向(正面/背面),因为区间翻转的连续性已经隐含在状态转移中——背面朝上只能从前一张背面延续或前一张正面开始新区间
    • 最优子结构:前 \(i\) 张卡片的最优解只依赖于前 \(i-1\) 张的状态,满足无后效性,适合动态规划
    • 适用于区间操作次数受限、状态具有连续性的序列优化类问题

【算法标签】

普及+ #线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;              // 常量:最大卡片数量

int n, k;                          // n: 卡片数量; k: 最多操作次数
int ans;                           // ans: 最大可能和
int a[N];                          // a[i]: 卡片 i 正面的数字
int b[N];                          // b[i]: 卡片 i 背面的数字
int dp[N][15][2];                  // dp[i][j][0/1]: 前 i 张卡片,用了 j 次操作,第 i 张卡片正面(0)/背面(1)朝上的最大和

signed main()
{
    cin >> n >> k;                 // 读入卡片数量和最多操作次数

    for (int i = 1; i <= n; i++)  // 读入每张卡片的正面和背面数字
        cin >> a[i] >> b[i];

    for (int i = 1; i <= n; i++)  // 初始化:0 次操作时所有卡片正面朝上
        dp[i][0][0] = dp[i - 1][0][0] + a[i];

    for (int i = 1; i <= n; i++)  // 枚举每张卡片
    {
        for (int j = 1; j <= min(i, k); j++)  // 枚举操作次数(不超过 i 和 k)
        {
            // 第 i 张正面朝上:第 i-1 张正反面均可,不新增操作
            dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]) + a[i];

            // 第 i 张背面朝上:第 i-1 张必须正面朝上(开始新翻转区间),或继续翻转区间
            dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0]) + b[i];
        }
    }

    for (int i = 0; i <= k; i++)  // 枚举所有可能的操作次数,取最大值
        ans = max({ans, dp[n][i][0], dp[n][i][1]});

    cout << ans << endl;           // 输出最大可能和

    return 0;
}

【运行结果】

7 2
2 1
6 9
3 5
9 2
4 8
7 4
5 6
45
posted @ 2026-07-13 15:10  团爸讲算法  阅读(37)  评论(0)    收藏  举报