P7514-[省选联考2021A/B卷]卡牌游戏【贪心】

正题

题目链接:https://www.luogu.com.cn/problem/P7514


题目大意

给出\(n\)个卡牌有\(a_i/b_i\),开始都是\(a_i\)朝上,将不超过\(m\)张卡牌变为\(b_i\)面朝上,使得朝上的数字中最大值减去最小值最小。

\(3\leq n\leq 10^6,1\leq m<n,1\leq a_i,b_i\leq 10^9\)


解题思路

虽然数据比较水,但是题目也是一道比较水的贪心题。

先离散化然后考虑暴力点的想法。枚举最大值和最小值\(l,r\),那么对于\(a_i\)\([l,r]\)之间的自然是不理,在之外的一定需要翻,如果翻转后存在\(b_i\)不在\([l,r]\)之间,那么显然行不通。

这样我们就可以\(O(n^2)\)了。

不难发现假设\([l,r]\)行的通那么\([l,r+1]\)用原来的方法一定也行得通,所以对于\(r\)递增,\(l\)也是不降的,双指针维护一下就好了。

时间复杂度\(O(n\log n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm> 
#include<cctype>
using namespace std;
const int N=2e6+10;
int n,m,ans,a[N],b[N],c[N],mx[N],mi[N],nx[N],ni[N],pos[N];
int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
bool cmp(int x,int y)
{return a[x]<a[y];}
bool check(int l,int r){
	int L=pos[l],R=pos[r+1]-1;
	int k=m-(n-R)-(L-1);
	if(k<0)return 0;
	if(mx[L-1]>r||mi[L-1]<l)return 0;
	if(nx[R+1]>r||ni[R+1]<l)return 0;
	return 1;
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=2*n;i++)a[i]=b[i]=read(),c[i]=i;
	sort(c+1,c+1+2*n,cmp);int cnt=2*n;
	for(int i=1;i<=2*n;i++)a[c[i]]=i;
	mi[0]=ni[n+1]=1e9+7;
	for(int i=1;i<=n;i++)mx[i]=max(a[i+n],mx[i-1]);
	for(int i=1;i<=n;i++)mi[i]=min(a[i+n],mi[i-1]);
	for(int i=n;i>=1;i--)nx[i]=max(a[i+n],nx[i+1]);
	for(int i=n;i>=1;i--)ni[i]=min(a[i+n],ni[i+1]);
	for(int i=1;i<=n;i++)pos[a[i]]=i;pos[cnt+1]=n+1;
	for(int i=cnt;i>=1;i--)
		if(!pos[i])pos[i]=pos[i+1];
	ans=1e9+7;int z=0;
	for(int i=1;i<=cnt;i++){
		while(z<i&&check(z+1,i))z++;
		if(z)ans=min(ans,b[c[i]]-b[c[z]]);
	}
	printf("%d\n",ans);
}
posted @ 2021-04-16 10:47  QuantAsk  阅读(114)  评论(0编辑  收藏  举报