贪心与对答案二分
Limited Repainting
给你一条由 n 个单元格组成的长条,所有单元格最初都是红色的。
在一次操作中,您可以选择一段连续的单元格,并将它们涂成蓝色。在绘制之前,所选单元格可以是红色或蓝色。请注意,不可能将它们涂成红色。您最多可以执行 k 次操作(可能为零)。
对于每个单元格,可以指定所有操作后所需的颜色:红色或蓝色。
显然,不可能总是在 k 次操作中满足所有要求。因此,我们还为每个单元格指定了惩罚值,如果单元格在所有操作后的颜色都是错误的,惩罚值就会生效。对于 i -th 单元,惩罚等于 ai 。
最终涂色的惩罚是根据所有涂错颜色的单元格中最大惩罚来计算的。如果没有这样的单元格,则涂色惩罚等于 0 。
最终涂色的最小罚分是多少?
输入
第一行包含一个整数 t ( 1≤t≤1e4 ) - 测试用例数。
每个测试用例的第一行包含两个整数 n 和 k ( 1≤n≤3⋅1e5 ; 0≤k≤n )--条带长度和最大操作次数。
第二行包含一个字符串 s ,由 n 字符 "R "和/或 "B "组成。R "表示将单元格涂成红色。B "表示应将单元格涂成蓝色。
第三行包含 n 个整数 a1,a2,…,an ( 1≤ai≤1e9 ) - 每个单元格的惩罚值。
所有测试用例的 n 之和不超过 3⋅1e5 。
输出
对于每个测试用例,打印一个整数 - 最终绘画的最小惩罚。
分析
所有涂错颜色的单元格中最大惩罚来计算的, 所以答案一定在给定的惩罚值中,或者0.
现在对这一段答案区间进行二分, (注意在二分前要先让这个答案集合单调递增)
先附上一个经典的二分模板
int l=0,r=n+1; // 假设是对1~n的一个数组进行二分
while(l+1!=r){ //循环条件为 l+1!=r
int mid=l+r>>1;
if( f() )r=mid; // f为判定函数, 根据题目需求自己写
else l=mid;
}
每次二分, 检测 以 符合当前惩罚值的 最高效 方案 染色, 是否能在 k 次染色次数用尽之前, 把条带的惩罚值降到当前假定的惩罚值
如果 k 够用, 说明还可以进一步染色使条带与目标颜色更加符合, 那就继续减小假定的惩罚值, 让二分中点 mid = r
如果 k 不够用, 那么就要少染一些, 提高假定的惩罚值, 让二分中点 mid = l
只有k次染色, 那么如何最高效地利用每一次染色机会呢?
对于每次二分假定的惩罚值, 扫一遍条带,
对于惩罚值大于当前假定最大惩罚值的第 i 个颜色, 如果目标是蓝色, 说明要进行染色, 那么已有的染色次数要 -1, 同时为了最高效染色,继续往下检测能不能继续染:
由于所有涂错颜色的单元格中最大惩罚来计算的, 所以对于小于等于这个惩罚值的第 i 个颜色:
直接染过去就行, 不用管它有没有染正确 (毕竟它正确与否都小于目前假定 的最大惩罚值, 对答案没有影响)
对于惩罚值大于当前假定最大惩罚值的第 i 个颜色:
如果是蓝色就染过去, 如果是红色, 则必须在此终止, 此次染色段结束( 不然你把红色染成染色, 这个染错的代价就比假定的最大惩罚值还大了)
对于小于当前惩罚值的, 无所谓, continue
AC代码
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define all(x) (x).begin(),(x).end()
#define endl '\n'
#define int long long
const int N=3e5+5; // 注意这里要开到3e5
int a[N];
int b[N];
int n,k;
string s;
int f(int x){
int t=k;
//s=' '+s;
rep(i,1,n){
if(s[i]=='B'){
if(a[i]>x){
t--;//先减,再看可以带上几个
for(int j=i+1;j<=n;j++){
i=j;
if(s[j]=='R'){
if(a[j]<=x)continue;
else break;
}
}
}
}
}
if(t>=0)return 1;
else return 0;
}
void solve(){
cin>>n>>k;
cin>>s;
s=' '+s; // 字符串从1开始编号, 所以在开头预处理加个' '
vector<int> v; // 用来存答案集合, vector先存一下便于排序去重, 然后放到b里面便于二分
v.push_back(0);
rep(i,1,n)cin>>a[i],v.push_back(a[i]);
// 排序去重
sort(all(v));
v.erase(unique(all(v)),v.end());
//放到b数组里面
int p=1;
for(auto i:v){
b[p++]=i;
}
// v的大小, 也就是这p-1个数, 都是可能的答案
int l=0,r=p;
while(l+1!=r){
int mid=l+r>>1;
if(f(b[mid]))r=mid;
else l=mid;
}
cout<<b[r]<<endl;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _;cin>>_;while(_--)
solve();
return 0;
}
一些做题过程中的脑残行为: 最开始没有把0 丢进去, 而是在二分完之后判断一下如果是最小的惩罚值, 再把 0 带进去扫一遍检测一下,
然后当时没有输入完s之后马上 s=' '+s, 而是每次在f()函数里进行这个操作, 之后如果还要进行上述对 0 是否符合的检测会多做一遍 s=' '+s, 导致了一些错误
题目来自 Educational Codeforces Round 175 (Rated for Div. 2) C. Limited Repainting

浙公网安备 33010602011771号