题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 张卡片,每张卡片正面为 \(A_i\)、背面为 \(B_i\),初始全部正面朝上。最多进行 \(K\) 次区间翻转操作(每次选择区间 \([l,r]\) 将区间内所有卡片翻面),求最终朝上面数字之和的最大值。这是一个线性动态规划问题,核心在于将区间翻转操作转化为卡片状态的连续变化,利用"翻转次数的奇偶性"和"区间操作的连续性"设计状态。
-
算法选择:
- 一维线性 DP:设 \(dp[i][j][0/1]\) 表示前 \(i\) 张卡片、使用了 \(j\) 次操作、第 \(i\) 张卡片正面朝上(\(0\))或背面朝上(\(1\))时的最大和
- 状态转移分析:利用区间翻转的连续性——第 \(i\) 张卡片的状态取决于第 \(i-1\) 张卡片是否处于同一个翻转区间内
-
关键步骤:
- 初始化:\(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\)
- 取两者最大值
- 第 \(i\) 张正面朝上(\(dp[i][j][0]\)):
- 答案提取:\(ans = \max_{0 \leq j \leq K}(dp[N][j][0], dp[N][j][1])\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot K)\),双层循环,每层状态转移为 \(O(1)\)
- 空间复杂度:\(O(N \cdot K)\),三维 DP 数组(可优化为 \(O(K)\) 滚动数组)
-
线性 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
浙公网安备 33010602011771号