Codeforces Round 1022 (Div. 2) D. Needle in a Numstack(二分)

每周至少五篇博客:(2/5)

https://codeforces.com/contest/2108/problem/D

题意

这是一个互动问题。

您在阁楼中找到了数字 kkknnn ,但丢失了两个数组 AAABBB

你记得那个:

  • ∣A∣+∣B∣=n|A| + |B| = nA+B=n ,数组的总长度为 nnn
  • ∣A∣≥k|A| \geq kAk∣B∣≥k|B| \geq kBk ,每个数组的长度至少为 kkk
  • 数组仅由 111kkk 的数字组成。
  • 如果您从数组 AAA 中获取任何 kkk 连续元素,它们都会有所不同。另外,如果您从数组 BBB 中获取任何 kkk 连续元素,它们都会有所不同。

幸运的是,定居在阁楼中的一种善良的精神发现了这些阵列,并将它们串成一个长度 CCC 的阵列 nnn 。也就是说,数组 AAA 的元素首先写入数组 CCC ,然后是数组 BBB 的元素。

您可以向 250250250 问题提出善良的精神。每个问题都包含一个索引 iii1≤i≤n1 \leq i \leq n1in )。作为响应,您将收到串联阵列 CCCiii \ - 的 iii \ - 。

您需要找到数组的长度 AAABBB ,或报告不可能唯一确定它们。

思路

看的哥哥的代码补的

因为保证 A,BA, BA,B 两个数组任意连续长度为 kkk 的连续子数组都不包含重复元素,所以 A,BA, BA,B 两个数组是分别由不同的一段长度 kkk 的排列循环构成的数组,那么我们分别在 [1,k][1, k][1,k][n−k+1,n][n - k + 1, n][nk+1,n] 两个区间用 k+kk + kk+k 次询问就可以得知构成 A,BA, BA,B 两个数组的排列,并且把通过询问得到的排列分别定义为 a,ba, ba,b 数组

接下来二分分界线,每次查询一个长度为 kkk 的区间,判断这个区间是 aaa 的循环排列还是 bbb 的循环排列,如果查询到的不是一个排列那么分界线肯定在这个区间中。不过因为只能查询 250250250 次,所以哪怕是二分其实也是不够用的

实际上我们只需要关注 a,ba, ba,b 不同的位置即可,只需要查询一个位置,这个位置通过计算可以判断是否属于 aaa 对应的位置来进行二分

定义 diffdiffdiff 数组为 a,ba, ba,b 中不同元素的位置的下标集合,定义 tottottot[k+1,n−k][k + 1, n - k][k+1,nk] 区间总共会有多少个有可能是分界线的位置,因为对于一处 a,ba, ba,b 不同元素的位置,在 CCC 数组中同样每隔 kkk 个位置就会重复一次排列,所以对于 [1,k][1, k][1,k] 中,如果 ai≠bia_i \ne b_iai=bi 那么有 tot←tot+(n−2×k)/ktot \gets tot + (n - 2 \times k) / ktottot+(n2×k)/k ,其中 n−2×kn - 2 \times kn2×k 是因为去掉两段已经得知的 a,ba, ba,b ,此外因为 nnn 不一定是 kkk 的整数倍,所以如果 i≤(n−2×k)mod  ki \le (n - 2\times k) \mod ki(n2×k)modk 并且也有 ai≠bia_i \ne b_iai=bi ,那么 tot←tot+1tot \gets tot + 1tottot+1

此时我们二分的是第 xxx 个有可能作为分界线的地方,所以我们还需要一个映射函数,用于映射出第 xxx 个有可能作为分界线的地方在 CCC 数组中的下标。如果查询到的值可以对应 aaa 中对应的位置的元素,说明分界线不会在当前二分的地方的左侧,否则不会在右侧

如果无法准确查找到分界线的话就输出 −1-11 ,否则可以找到 A,BA,BA,B 的长度

总询问次数不会超过 200200200

代码

void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(k), b(k);
    auto get = [&](int i) {
        std::cout << "? " << i + 1 << std::endl;
        int x;
        std::cin >> x;
        return x;
    };

    for (int i = 0; i < k; i ++) a[i] = get(i);
    for (int i = n - k; i < n; i ++) b[i % k] = get(i);

    std::vector<int> diff;
    int tot = 0;
    for (int i = 0; i < k; i ++) if (a[i] != b[i]) {
        diff.emplace_back(i);
        tot += (n - 2 * k) / k;
        if (i < n % k) tot ++;
    }

    auto get2 = [&](int i) ->int {
        if (i == -1) return k - 1;
        if (i == tot) return n - k;
        return i / diff.size() * k + diff[i % diff.size()] + k;
    };

    int l = -1, r = tot;
    while (l + 1 < r) {
        int mid = (l + r) >> 1;
        int i = get2(mid);
        if (get(i) == a[i % k]) l = mid;
        else r = mid;
    }

    l = get2(l), r = get2(r);
    if (l + 1 == r) std::cout << "! " << r << " " << n - r << std::endl;
    else std::cout << "! -1" << std::endl;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值