P7514 [省选联考 2021 A/B 卷] 卡牌游戏 题解

P7514 省选联考 2021 A/B 卷 卡牌游戏 题解

思路

考虑枚举最小值。

首先按照 aia_iai 从小到大给卡牌排序,然后枚举 iii。

当 aia_iai 作为最小值时,前 i−1i-1i−1 个卡牌肯定要翻,然后如果还有多余次数就选择一段最长的 ai>bia_i>b_iai>bi 的后缀,把这个后缀的卡牌全部反转后的卡牌就是 aia_iai 作为最小值是的最优序列。

因为 aia_iai 从小到大排序,所以有 aj(1≤j<i)<aia_j(1 \le j < i)< a_iaj(1≤j<i)<ai,就不符合 aia_iai 是最小值的条件,所以必须翻转。必须选择一段连续的 ai>bia_i>b_iai>bi 的后缀翻转是因为如过不是连续的,假设最后没有翻转的卡牌是 iii,则最大值一定 ≥ai\ge a_i≥ai,那么所有在 iii 前的翻转是没有意义的。

当 bib_ibi 作为最小值时,和 aia_iai 类似,二分找到 jjj 使前 jjj 个卡牌必须翻转,然后找一段最长的 ai>bia_i>b_iai>bi 的后缀即可。

时间复杂度 O(nlog⁡n)O(n \log n)O(nlogn)。

细节非常多,详见代码。

代码

cpp 复制代码
#include<bits/stdc++.h>//包含常用头文件
using namespace std;//使用标准命名空间
typedef long long ll;//定义长整型别名
int n,m;//卡牌数量和最多翻面次数
struct N{//卡牌结构体
	int a,b;//正面和背面的数字
}a[1000010];//卡牌数组
bool cmp(N a,N b){//排序比较函数
	if(a.a!=b.a)return a.a<b.a;//首先按正面数字升序排序
	return a.b>b.b;//正面相同时按背面数字降序排序
}
int suf[1000010],smn[1000010],smx[1000010],pmn[1000010],pmx[1000010];//后缀和前缀的统计数组
int st[1000010][21],lg2[1000010];//ST表及预处理对数数组
inline int max(int a,int b){//自定义求最大值
	return a>b?a:b;
}
inline int min(int a,int b){//自定义求最小值
	return a<b?a:b;
}
int get(int l,int r){//ST表查询区间最大值
	if(l>r)return 0;//区间不合法返回0
	int d=lg2[r-l+1];//计算区间长度的对数
	return max(st[l][d],st[r-(1<<d)+1][d]);//返回两个子区间的最大值
}
int main(){
	ios::sync_with_stdio(0);//关闭同步流以加速输入输出
	cin.tie(0);//解除cin和cout的绑定
	cin>>n>>m;//读入卡牌数和翻面限制
	for(int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1;//预处理以2为底的对数
	for(int i=1;i<=n;i++){
		cin>>a[i].a;//读入每张卡牌正面的数字
	}
	for(int i=1;i<=n;i++){
		cin>>a[i].b;//读入每张卡牌背面的数字
	}
	sort(a+1,a+1+n,cmp);//按照自定义规则对卡牌进行排序
	for(int i=1;i<=n;i++)st[i][0]=a[i].b;//初始化ST表的第0层为每张卡牌背面的数字
	for(int i=1;i<=20;i++){//构建ST表
		for(int x=1;x+(1<<i)-1<=n;x++){
			st[x][i]=st[x][i-1]>st[x+(1<<i-1)][i-1]?st[x][i-1]:st[x+(1<<i-1)][i-1];//状态转移
		}
	}
	smn[n+1]=1e9;//初始化后缀最小值为无穷大
	int ls=1;//未使用的变量
	for(int i=n;i;i--){//从后往前预处理后缀信息
		suf[i]=suf[i+1]+(a[i].a>a[i].b);//统计后缀中正面大于背面的卡牌数量
		smn[i]=min(smn[i+1],min(a[i].a,a[i].b));//维护后缀中min(a,b)的最小值
		smx[i]=max(smx[i+1],min(a[i].a,a[i].b));//维护后缀中min(a,b)的最大值
	}
	pmn[0]=1e9;//初始化前缀最小值为无穷大
	for(int i=1;i<=n;i++){//从前往后预处理前缀信息
		pmn[i]=min(pmn[i-1],a[i].b);//维护前缀中背面数字的最小值
		pmx[i]=max(pmx[i-1],a[i].b);//维护前缀中背面数字的最大值
	}
	int mx=0,la=0;//mx记录最大值,la记录相同正面数字的左边界
	int ans=1e9;//初始化答案为无穷大
	for(int i=1;i<=n;i++){//枚举每张卡牌作为最小值的情况
		if(a[i].a!=a[i-1].a)la=i;//更新相同正面数字的左边界
		if(pmn[la-1]>=a[i].a&&i-1<=m){ //情况1:当前卡牌正面数字作为最小值
			int now=m-(la-1);//计算剩余的翻面次数
			int l=i+1,r=n+1;
			while(l<r){//二分寻找可以翻转的后缀的左边界
				int mid=(l+r)>>1;
				if(suf[mid]<=now&&smn[mid]>=a[i].a)r=mid;//如果翻转次数足够且最小值合法,向左逼近
				else l=mid+1; 
			}
			ans=min(ans,max({pmx[la-1],smx[r],a[r-1].a})-a[i].a);//更新极差的最小值
		}
		int l=0,r=n;
		while(l<r){//情况2:当前卡牌背面数字作为最小值,二分寻找必须翻转的前缀右边界
			int mid=(l+r+1)>>1;
			if(a[mid].a<a[i].b)l=mid;//如果正面数字小于当前最小值,则必须翻转
			else r=mid-1;
		}
		if(pmn[r]<a[i].b||r>m)continue;//如果前缀背面最小值不合法或翻转次数超限,则跳过
		int now=m-r;//计算剩余的翻面次数
		int mx=(r>=i?max(get(1,i-1),get(i+1,r)):get(1,r));//计算前r张卡牌中未翻转卡牌的最大值
		l=r+1;r=n+1;
		while(l<r){//二分寻找可以翻转的后缀的左边界
			int mid=(l+r)>>1;
			if(suf[mid]<=now&&smn[mid]>=a[i].b&&suf[mid]-suf[mid+1])r=mid;//满足翻转条件且当前卡牌需要翻转
			else l=mid+1; 
		}
		mx=max(mx,max(smx[r],(r-1==i?a[r-2].a:a[r-1].a)));//计算整体最大值
		ans=min(ans,mx-a[i].b);//更新极差的最小值
	}
	cout<<ans;//输出最终的最小极差
	return 0;
}
相关推荐
residual_fan1 小时前
经验模态重构:直向工业时间序列数据的数据扩增方法
人工智能·算法·重构·数据挖掘·数据分析
程序猿编码1 小时前
LLM 技术迁移音频领域:基于 GGML 搭建 Roformer,端侧音乐人声分离实现详解
c++·llm·音视频·transformer·模型推理·ggml
wuminyu1 小时前
Kafka的写入延迟与磁盘IO抖动原理分析
java·linux·c语言·jvm·c++
buhuizhiyuci1 小时前
线程的互斥
linux·运维·服务器·c++
aramae1 小时前
模拟实现strcpy(字符串拷贝)(C语言)
java·c语言·开发语言·算法
Benny_Tang1 小时前
「雅礼集训 2018 Day7」A 题解
数据结构·c++·算法
Doubbbbbbble云1 小时前
跳表结构在高并发系统中的应用与优势分析4
算法
TAN-90°-1 小时前
Deep Learning for Computer Vision——Large Scale Distributed Training
人工智能·深度学习·神经网络·算法·机器学习·计算机视觉·语言模型
m0_734571761 小时前
深入理解C++ 构造函数<十一> initializer_list 构造
开发语言·c++