[题解] Range Flip - AtCoder abc466_E

Range Flip - AtCoder abc466_e

为了更细致的刻画翻牌后的影响,我们令 \(c_i=a_i+b_i\),于是有 \(a_i=b_i+c_i\),所以 \(s_a=s_b+s_c\),由于 \(s_b\) 是定值,我们只需要最大化 \(s_c\) 即可。

那问题来了,怎么处理区间翻牌呢?

首先不难发现,选择几个有公共部分的区间,可以被相同数量的另外几个没有交集的区间等价替换,所以为了简化思考,我们只需考虑选没有重叠的区间即可。

假设我们要翻第 \(i\) 张牌,在翻牌前,肯定有 \(a_i=b_i+c_i\),而在翻牌后,我们肯定还想让 \(a_i=b_i+c_i\),这样不用改变算 \(s_a\) 的方式。

于是可以很显然的发现,把 \(c_i \gets 0\) 就能实现我们的目的。

所以题目转化为了:我们有一个序列 \(c_i\),要从中选出至多 \(k\) 个区间,把所有选出来的值都置成 0,求出操作后序列的和最大是多少。

为了最大化 \(s_c\),我们必须最小化选出来的和,这种序列上选区间的问题考虑 DP 求解。

\(f[i,j,0/1]\) 表示在前 \(i\) 个数中,选出了 \(j\) 个区间后的最大和,因为有些可以不选,所以再加一维 \(0/1\) 表示第 \(i\) 个是否被选。不难得到转移方程:

\[\begin{aligned}f[i,j,0]&=min\{f[i-1,j,0],f[i-1,j,1] \} \\ f[i,j,1]&=min\{f[i-1,j-1,0],f[i-1,j-1,1],f[i-1,j,1] \}+c[i] \end{aligned} \]

于是这个题就被我们愉快的解决了。

实现细节

  1. 关于 \(f\) 的初始值,首先 \(\forall i\in[0,n],f[i,0,0]=0\),意思就是前 \(i\) 个里面一个都不选的答案,注意不要漏掉0。然后为了求最小值我们还需要给其他的 \(f\) 赋值为 \(+\infin\)
  2. 题目要求我们 至多选 \(k\) 个区间,所以最后应该对 \(f\)\(\max\) 而不是直接写 \(f[n,k,0/1]\)
  3. 第二条的补充,全不选也是一种方案,所以 \(ans\) 初值应该为 \(0\) 而不是 \(+\infin\)
  4. 一定要弄清楚 \(f\) 存的是被选的 \(c\) 的和,选完后没被选的和应该 \(=\) 最开始 \(c\) 的和 \(-\) 选出的 \(c\) 的和,并且最后还得再加上 \(b\) 的和才是最终要输出的答案。
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+10, INF=1e16;
int c[N], f[N][15][2];
int n,k,ans;

signed main(){
	cin.tie(0)->sync_with_stdio(0);
	cin>>n>>k;
	int s=0, sc=0;
	for(int i=1; i<=n; i++){
		int x, y; cin>>x>>y;
		c[i]=x-y;
		s+=y;
		sc+=c[i];
	}
	fill_n(&f[0][0][0], N*15*2, INF);
	f[0][0][0]=0;
	for(int i=1; i<=n; i++) f[i][0][0]=0;// 全不选的答案为0
	for(int i=1; i<=n; i++){
		for(int j=1; j<=k; j++){
			f[i][j][0]=min(f[i-1][j][0], f[i-1][j][1]);
			f[i][j][1]=min({f[i-1][j-1][0], f[i-1][j-1][1], f[i-1][j][1]})+c[i];
		}
	}
	ans=0;// 全不选
	for(int i=1; i<=k; i++){
		ans=min({ans, f[n][i][0], f[n][i][1]});
	}
	cout<<s+sc-ans;// c的总和=原来c的和-选的和,注意还要加上a的和
	return 0;
}
posted @ 2026-07-12 12:31  Cute_lxy  阅读(39)  评论(0)    收藏  举报