Codeforces Round#770(Div.2)D. Finding Zero

题目

这是一个交互问题。

我们选择了一个整数数组 a1,a2,…,an (0≤ai≤109) 并在其中隐藏了一个零!你的目标是找到这个零的位置,也就是找到 i 使得 ai=0。

您可以进行多次查询以猜测答案。对于每个查询,您可以想出三个不同的索引 i,j,k,我们将告诉您 max(ai,aj,ak)−min(ai,aj,ak) 的值。换句话说,我们将告诉您 ai、aj 和 ak 中最大和最小数之间的差异。

您可以进行不超过 2⋅n−2 次查询,然后您有两次尝试猜测零在哪里。也就是说,你必须告诉我们两个数字 i 和 j,如果 ai=0 或 aj=0,你就赢了。

你能猜到我们把零藏在哪里了吗?

请注意,每个测试用例中的数组都是预先固定的,在游戏过程中不会改变。换句话说,交互者不是自适应的。

输入

每个测试包含多个测试用例。第一行包含测试用例的数量 t (1≤t≤500)。测试用例的描述如下。

每个测试用例的第一行也是唯一一行包含一个整数 n (4≤n≤1000)——我们选择的数组的长度。

保证所有测试用例的 n 总和不超过 3000。

相互作用
对于每个测试用例,交互从读取 n 开始。

要进行查询,请打印“? i j k”(不带引号,1≤i,j,k≤n,索引必须是不同的)。然后你应该从标准输入中读取我们的响应,即 max(ai,aj,ak)−min(ai,aj,ak)。

如果响应为 -1,则意味着您的程序进行了无效查询或尝试次数已用完。您的程序必须在读取 -1 后立即终止,您将得到一个判定错误答案。否则你可能会得到任何判决,因为程序将继续从关闭的流中读取。请注意,如果查询正确,则答案永远不会是 -1,因为 max(ai,aj,ak)-min(ai,aj,ak)≥0。

要给出最终答案,请打印“!i j”(不带引号)。允许将相同的数字打印两次(即 i=j)。请注意,给出此答案不计入 2⋅n−2 个查询的限制。之后,您的程序必须继续解决剩余的测试用例,或者如果所有测试用例都已解决,则退出。

题解

请注意,对于任意四个数字 a、b、c、d,我们可以仅使用以下四个查询来找到其中至少两个肯定不为零的数字。

对于四个数字中的每一个,计算它的值,即其他三个数字的最大值和最小值之差:f(a)=max(b,c,d)−min(b,c,d) 等等。这恰好需要四个查询。

现在,考虑如果四个数字之一是零会发生什么。例如,如果 a=0,b≤c≤d,则:

f(a)=d−b
f(b)=d
f©=d
f(d)=c
d>d−b,d≥c,所以两个最大的f(b)=f(d)(。当然,值的顺序可能不同,但具有两个数字将始终保证非零。

如果这些数字中没有零,那么我们仍然可以运行这个算法,因为它会产生哪些数字并不重要——反正它们都是非零的。

现在让我们学习如何使用该算法解决问题。从前四个数字的“一堆”开始,应用算法并扔掉两个特定的非零。将接下来的两个数字添加到“堆”中并再次删除两个非零。重复此操作,直到“堆”中剩下两个或三个数字,具体取决于 n 的奇偶性。如果还剩下三个元素,再次添加一些我们已经丢弃到堆中的数字并应用最后一次算法。

如果 n 是偶数,我们进行了 n−2/2⋅4=2n−4 个查询。
如果 n 是奇数,我们进行了 n−3/2⋅4+4=2n−2 个查询。
该解决方案的复杂度为 O(n),并且该解决方案使用不超过 2n-2 个查询。


#include<bits/stdc++.h>
using namespace std;
int get(vector<int>x)
{
    cout << "? " << x[0] + 1 << " " << x[1] + 1 << " " << x[2] + 1 << endl;
    int res;
    cin >> res;
    return res;
}
int main()
{
    cin.tie(nullptr);
    int t; cin >> t;
    while (t--)
    {
        int n; cin >> n;
        pair<int, int>ans = { 0,1 };
        for (int i = 2; i < n - 1; i += 2)
        {
            vector<pair<int, int>>temp(4);
            vector<int>query = { ans.first,ans.second,i,i + 1 };
            for (int j = 0; j < 4; j++)
            {
                vector<int>now = query;
                now.erase(now.begin() + j);
                temp[j] = { get(now),query[j] };
            }
            sort(temp.begin(), temp.end());
            ans = { temp[0].second,temp[1].second };
        }
        if (n % 2 == 1)
        {  
            int dif = 0;
            while (dif == ans.first || dif == ans.second)dif++;
            vector<pair<int, int>>temp(4);
            vector<int>query = { ans.first,ans.second,n-1,dif };
            for (int j = 0; j < 4; j++)
            {
                vector<int>now = query;
                now.erase(now.begin() + j);
                temp[j] = { get(now),query[j] };
            }
            sort(temp.begin(), temp.end());
            ans = { temp[0].second,temp[1].second };
        }
        cout <<"!"<<" "<< ans.first+1 << " " << ans.second+1 << endl;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值