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

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

题目描述

Alice 有 \(n\) 张卡牌,第 \(i\)\(1 \le i \le n\))张卡牌的正面有数字 \(a_i\),背面有数字 \(b_i\),初始时所有卡牌正面朝上。

现在 Alice 可以将不超过 \(m\) 张卡牌翻面,即由正面朝上改为背面朝上。Alice 的目标是让最终朝上的 \(n\) 个数字的极差(最大值与最小值的差)尽量小。请你帮 Alice 算一算极差的最小值是多少。

【数据范围】

对于所有测试数据:\(3 \le n \le {10}^6\)\(1 \le m < n\)\(1 \le a_i, b_i \le {10}^9\)

每个测试点的具体限制见下表:

测试点编号 \(n \le\) 特殊限制
\(1 \sim 2\) \(10\)
\(3 \sim 4\) \(500\)
\(5 \sim 6\) \(5 \times {10}^5\) \(m \le 1000\)
\(7\) \({10}^5\)
\(8\) \(4 \times {10}^5\)
\(9\) \(7 \times {10}^5\)
\(10\) \({10}^6\)

思路:

首先观察一下题目,我们很容易就会发现这个答案是具有单调性的,也就是对于 \(\forall x,x\in \mathbb{R},check(x)=\{0,0,0,0,0,1,1,1\}\)

然后我们就可以考虑二分。

二分一个答案代表上界,然后现在问题就转换为如何判断原序列经过若干次反转后极差能否 \(<n\)

为了解决这个问题,我们需要引出一个结论:最后翻转的区间一定最开始的一段和最后面的一段。

下面来简答证明一下这个结论的正确性(其实还是比较感性的)

首先我们先设写出一个序列:
\(\large 000011\textcolor{red}{1}1100000\)

我们令 \(0\) 表示翻转这个位置,\(1\) 表示不翻转

设翻转的位置为 \(i\),最左边且未翻转的值为 \(y\),最右边且未翻转的值为 \(x\)。左边翻转的最大值为 \(mx1\),左边翻转的最小值为 \(mn1\);右边翻转的最大值为 \(mx2\),右边翻转的最小值为 \(mn2\)

然后我们列出在不反转 \(i\) 的时候答案应该是多少:\(\max(x,mx1,mx2)-\min(y,mn1,mn2)\)

我们假设翻转过后的 \(b_i>x\),则原式变为 \(\max(b_i,mx1,mx2)-\min(y,mn1,mn2)\)
然后我们不难发现,如果我们在这个位置单独翻转和从右侧包含过来的情况其实应该是等价的,即依然可以转换为上述一开始的式子。

如果 \(b_i<y\) 同理。

所以梳理一下解法:

  • 二分一个上界
  • 固定分界点 \(l\),然后尺取分界点 \(r\)
  • 固定分界点 \(r\),然后尺取分界点 \(l\)
放一下代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mem(a) memset(a,0,sizeof(a))
#define set(a,b) memset(a,b,sizeof(a))
#define ls i<<1
#define rs i<<1|1
#define pb push_back
#define pt putchar
#define All(a) a.begin(),a.end()
#define T int t;cin>>t;while(t--)
#define rand RAND
using namespace std;
char buf[1<<20],*p1,*p2;
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
template<class Typ> Typ &re(Typ &x){char ch=gc(),sgn=0; x=0;for(;ch<'0'||ch>'9';ch=gc()) sgn|=ch=='-';for(;ch>='0'&&ch<='9';ch=gc()) x=x*10+(ch^48);return sgn&&(x=-x),x;}
template<class Typ> void wt(Typ x){if(x<0) putchar('-'),x=-x;if(x>9) wt(x/10);putchar(x%10^48);}
const int inf=1e18;
const int maxn=1e6+5;
const int mod=1e9+7;
int seed = 19243;
unsigned rand(){return seed=(seed*48271ll)%2147483647;}
int n,m;
int a[maxn],b[maxn];
int pre_mn[maxn],pre_mx[maxn];
int suf_mn[maxn],suf_mx[maxn];
void RMQ(){
    for(int i=0;i<=n+1;i++)pre_mn[i]=suf_mn[i]=inf,pre_mx[i]=suf_mx[i]=-inf;
    for(int i=1;i<=n;i++)pre_mn[i]=min(pre_mn[i-1],b[i]),pre_mx[i]=max(pre_mx[i-1],b[i]);
    for(int i=n;i>=1;i--)suf_mn[i]=min(suf_mn[i+1],b[i]),suf_mx[i]=max(suf_mx[i+1],b[i]);
}
bool check(int x){
    for(int l=1,r=1;l<=n;l++){
        while(r<=n&&a[r]-a[l]<=x){
            if((n-(r-l+1)<=m) and max({a[r],pre_mx[l-1],suf_mx[r+1]})-min({a[l],pre_mn[l-1],suf_mn[r+1]})<=x)return 1;
            r++;
        }
        r--;
    }
    for(int r=n,l=n;r>=1;r--){
        while(l>=1&&a[r]-a[l]<=x){
            if((n-(r-l+1)<=m) and max({a[r],pre_mx[l-1],suf_mx[r+1]})-min({a[l],pre_mn[l-1],suf_mn[r+1]})<=x)return 1;
            l--;
        }
        l++;
    }
    return 0;
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    RMQ();
    int l=0,r=a[n]-a[1],ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;
    return 0;
}
posted @ 2023-11-09 22:10  Candycar  阅读(91)  评论(0)    收藏  举报