[题解] 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}
\]
于是这个题就被我们愉快的解决了。
实现细节
- 关于 \(f\) 的初始值,首先 \(\forall i\in[0,n],f[i,0,0]=0\),意思就是前 \(i\) 个里面一个都不选的答案,注意不要漏掉0。然后为了求最小值我们还需要给其他的 \(f\) 赋值为 \(+\infin\)。
- 题目要求我们 至多选 \(k\) 个区间,所以最后应该对 \(f\) 取 \(\max\) 而不是直接写 \(f[n,k,0/1]\)。
- 第二条的补充,全不选也是一种方案,所以 \(ans\) 初值应该为 \(0\) 而不是 \(+\infin\)。
- 一定要弄清楚 \(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;
}

浙公网安备 33010602011771号