CF1973D(CFR 945) Cat, Fox and Maximum Array Split 题解

本题解同步于我的个人博客

题意

题目链接

交互题。

狐狸给你两个正整数 n , k n,k n,k,并且她有一个长度为 n n n 的隐藏数组。对于一段区间 ( l , r ) (l,r) (l,r),其价值 f ( l , r ) f(l,r) f(l,r) 等于长度乘以区间最大值。

你有 2 n 2n 2n 次查询次数,每次查询形如 ? l x,狐狸会告诉你一个数 r r r,表示最小的满足 f ( l , r ) = x f(l,r)=x f(l,r)=x r r r,或是 n + 1 n+1 n+1 表示不存在 r r r

你的目标是找到最大的 m m m,使得原数组能够被分割成 k k k 个子数组,且每个子数组的价值都为 m m m

解析

爱做交互题。

根据对范围 2 n 2n 2n 的猜测,容易想到可以将查询分成两块查询次数分别不多于 n n n 次的查询。发现数组的最大值是至关重要的,因而我们可以通过至多 n n n 次形如 ? 1 n*i 的查询( i i i n n n 1 1 1)判断数组的最大值是多少。我们发现只有当数组的最大值为 i i i 时,上述询问才会返回一个唯一的 r = n r=n r=n

找到最大值后,不妨考虑 m m m 的可能值。由于在这 k k k 个区间后,必定有一个区间包含最大值(记作 m a x max max),且这个区间的长度一定不会超过 ⌊ n k ⌋ \lfloor \frac{n}{k} \rfloor kn。这是因为如果这个包含最大值的区间长度比 ⌊ n k ⌋ \lfloor \frac{n}{k} \rfloor kn 要大,那么这 k k k 的区间的长度和一定会超过 n n n。因此,我们可以枚举 m m m 的可能性,即从 m a x × ⌊ n k ⌋ max \times \lfloor \frac{n}{k} \rfloor max×kn m a x × 1 max \times 1 max×1

对于每个可能的 m m m,我们可以这样判断其合法性:先询问 ? 1 m,然后若返回 n + 1 n+1 n+1,显然不合法,否则若返回 r r r,再询问 ? r+1 m 即可。以此类推,我们可以通过至多 k k k 次询问来判断这个 m m m 是否合法。

具体的,在询问过程中,以下情况均为不合法:

  1. 任意一次询问返回了 n + 1 n+1 n+1
  2. 经过 k k k 次询问后,数组仍然没有被全部覆盖。
  3. 还没有询问 k k k 次,数组就被全覆盖了。

您可以通过查询代码中的 check 函数来理解详细的验证过程。

你发现这一部分的查询次数为 ⌊ n k ⌋ × k \lfloor \frac{n}{k} \rfloor \times k kn×k,不会超过 n n n。故问题解决。

代码

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define pi pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
using namespace std;
int n,k,maxx;
bool check(int len){
    int now=1,nowk=0;
    while(now<=n&&nowk<k){
        printf("? %lld %lld\n",now,maxx*len);
        fflush(stdout);
        int tmp;   
        scanf("%lld",&tmp);
        if(tmp==n+1) return false;
        now=tmp+1;nowk++;
    }
    if(now>n&&nowk==k) return true;
    return false;
}
void solve(){
    scanf("%lld %lld",&n,&k);
    for(int i=n;i>=1;i--){
        printf("? 1 %lld\n",i*n);
        fflush(stdout);
        int tmp;
        scanf("%lld",&tmp);
        if(tmp!=n+1){
            maxx=i;break;
        }
    }
    for(int i=n/k;i>=1;i--){
        if(check(i)){
            printf("! %lld\n",i*maxx);
            fflush(stdout);
            int tmp;
            scanf("%lld",&tmp);
            if(tmp==-1) exit(0);
            return;
        }
    }
    printf("! -1\n");
    fflush(stdout);
    int tmp;
    scanf("%lld",&tmp);
    if(tmp==-1) exit(0);
    return;
}
signed main(){
    int T;
    scanf("%lld",&T);
    while(T--) solve();
    return 0;
}
  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值