[省选联考 2021 A/B 卷] 卡牌游戏
大意
给你一些卡牌,正面是 \(a_i\),反面是 \(b_i\),现在全部是正面朝上,要求翻转一些卡牌,使得朝上的一面所有数的极差最小,求这个最小的极差。
Sol
这题我记得场上我搞了一个能过样例的东西最后拿了 90pts。然后出来发现假算不用过样例/qd。
现在回过头去想想好想正解并不困难。你考虑这样一件事,这题是要你求最大的最小,那么容易想到二分来做。也就是你二分这个极差,然后接下来就是如果我们知道值域范围的左端点,那么我们就知道右端点了,这样子我们扫一遍判断是否一张卡牌正反面中至少有一个在值域中。可是你发现值域范围是 \(10^9\),直接枚举左端点显然不现实。于是我们先考虑一个贪心,就是左端点一定是选择出现过的数中的,这个非常显然,这样我们每次 \(O(n)\) 枚举左端点,想查询是不是所有的卡牌都合法,由于给你的 \(a\) 是升序的,我们相当于查询一段前缀和一段后缀是否合法那这两段 \(a\) 已经是不行的了,就看 \(b\) 可不可行。那其实就只要看极值有没有跑出去,所以预处理前后缀的 \(b\) 的 \(\operatorname{RMQ}\) 就可以了。
两只 \(\operatorname{log}\) 被卡常了/ll。要用双指针日掉一只。
Code
// Problem: P7514 [省选联考 2021 A/B 卷] 卡牌游戏
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7514
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Time: 2022-04-16 09:05:01
#include<bits/stdc++.h>
#define int long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb emplace_back
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
using namespace std;
const int MAXN=1e6+10;
int a[MAXN],b[MAXN];
int premin[MAXN],sufmin[MAXN];
int premax[MAXN],sufmax[MAXN];
int n,m;
vector<int> be;
bool check(int mid){
int l=1,r=1,rv;
for(int lv:be){
rv=lv+mid;
// pt(lv);pts(rv);
while(a[r+1]<=rv&&r<n) r++;
while(a[l]<lv&&l<r) l++;
// pt(l);pts(r);
if(n-r+l-1<=m&&premin[l-1]>=lv&&premax[l-1]<=rv&&sufmin[r+1]>=lv&&sufmax[r+1]<=rv)
return 1;
}
return 0;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
rep(i,1,n) cin>>a[i],be.pb(a[i]);
rep(i,1,n) cin>>b[i],be.pb(b[i]);
sort(be.begin(),be.end());
premin[0]=INF;
rep(i,1,n) premax[i]=max(premax[i-1],b[i]),premin[i]=min(premin[i-1],b[i]);
sufmin[n+1]=INF;
per(i,n,1) sufmax[i]=max(sufmax[i+1],b[i]),sufmin[i]=min(sufmin[i+1],b[i]);
int l=0,r=1000000000,ans=r;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}cout<<ans<<'\n';
return 0;
}