Codeforces Round #755 (Div. 2) D

Description

This is an interactive problem.

Jury initially had a sequence a a a of length n n n, such that a i = i a_i=i ai=i.

The jury chose three integers i , j , k i, j, k i,j,k, such that 1 ≤ i < j < k ≤ n , j − i > 1 1≤i<j<k≤n, j−i>1 1i<j<kn,ji>1. After that, Jury reversed subsegments [i,j−1] and [j,k] of the sequence a a a.

Reversing a subsegment [l,r] of the sequence a a a means reversing the order of elements a l , a l + 1 , … , a r a_l,a_{l+1},…,a_r al,al+1,,ar in the sequence, i. e. a l a_l al is swapped with a r a_r ar, a l + 1 a_{l+1} al+1 is swapped with a r − 1 a_{r−1} ar1, etc.

You are given the number n n n and you should find i i i, j j j, k k k after asking some questions.

In one question you can choose two integers l l l and r r r ( 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn) and ask the number of inversions on the subsegment [l,r] of the sequence a a a. You will be given the number of pairs ( i , j ) (i,j) (i,j) such that l ≤ i < j ≤ r l≤i<j≤r li<jr, and a i > a j a_i>a_j ai>aj.

Find the chosen numbers i , j , k i, j, k i,j,k after at most 40 40 40 questions.

The numbers i i i, j j j, and k k k are fixed before the start of your program and do not depend on your queries.

Input

Each test consists of multiple test cases. The first line contains a single integer t ( 1 ≤ t ≤ 100 ) t (1≤t≤100) t(1t100) — the number of test cases. Description of the test cases follows.

The single line of each test case contains a single integer n ( 4 ≤ n ≤ 1 0 9 ) n (4≤n≤10^9) n(4n109). After reading it you should start an interaction process by asking questions for that test case. After giving an answer you should:

  • Terminate your program if that is the last test case.
  • Proceed to the next test case otherwise.

Output

To ask number of inversions on a subsegment [l,r], print “? l r”, where ( 1 ≤ l ≤ r ≤ n ) (1≤l≤r≤n) (1lrn). You can ask at most 40 40 40 questions in each test case. As a result you should read a single integer x x x.

  • If x = − 1 x=−1 x=1, your program made an invalid question or you exceeded the number of questions for that test case. Your program should terminate immediately (otherwise it can get any verdict instead of “Wrong Answer”).
  • Otherwise x x x is equal to the number of inversions on the subsegment [l,r] of the sequence a a a.

To give the answer, print “! i j k”, where i , j , k i, j, k i,j,k are the numbers you found. You should continue solving the next test cases or terminate the program after that.

Solution

1.n的数据为1e9,而最多只有40次询问,毫无疑问只有二分能做。

二分至多要30次左右的询问,其他两点只能若干次查询,这就涉及到是二分找中间点还是两边的点。

找中间点j:

​ check函数可以写为该下标左边为一个逆序对的数,右边为另一个逆序对的数,但这个时候会有一个弊端。

就是一般逆序对的个数都是以 ( 1 + n ) ∗ n 2 \frac{(1+n)*n}{2} 2(1+n)n 的形式存在的,但是,如果以找中间点为二分,令 a n s ( n ) = ( 1 + n ) ∗ n 2 ans(n)=\frac{(1+n)*n}{2} ans(n)=2(1+n)n

例: a n s ( 10 ) = 55 ans(10)=55 ans(10)=55, a n s ( 9 ) = 45 ans(9)=45 ans(9)=45, a n s ( 4 ) = 10 ans(4)=10 ans(4)=10.

这样的情况下,check函数会以为这里两个逆序对的组合,而不是一个,所以找中间点显得尤为困难。

找左边的点i:

虽说可以,但不利于找其余两个点,故该题解选择找k(后面给出解释)。

2.二分找出k。

不难看出,在k之前的逆序对总数,就是这个区间的所有逆序对总数。

由此结论可以得出,我们只需要找出第一个不等于全区间逆序对个数的点就行了。

所以在此前,要先对[1, n]进行一次询问,获取全区间的逆序对个数。

3.找出j。

找出k之后,根据1中ans的定义(也就相当于单调递减序列里逆序对的个数),

假设[j, k]里逆序对的个数为10 (1+2+3+4),那么[j, k-1]的逆序对个数就为6 (1+2+3)

对于此,我们可以询问一次[1, k-1],将全区间的值和该结果相减,所得到的就是减少的逆序对个数,不难看出,这也是j和k的差值,此时j求出。

4.找出i。

找i的方法可以与找j的方法类似,只需要更新tot值即可。

而我们也可以通过1中的ans函数倒推。

由于 a n s ( n ) = ( 1 + n ) ∗ n 2 ans(n)=\frac{(1+n)*n}{2} ans(n)=2(1+n)n ,所以 n = i n t ( s q r t ( 2 ∗ a n s ( n ) ) ) n=int(sqrt(2*ans(n))) n=int(sqrt(2ans(n))),直接在j里减即可。

注意:前面的区间为[i, j-1],所以要额外减1。

Code

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007
#define intmax 2147483647
#define memmax 0x7fffffff

inline ll read()
{
    ll x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}

ll t;
ll n;
ll a[105], b[105];

ll query(ll left, ll right)
{
    ll tmp;
    cout << "? " << left << " " << right << endl;
    tmp = read();

    return tmp;
}

void print(ll i, ll j, ll k)
{
    cout << "! " << i << " " << j << " " << k << endl;
}

void solve()
{
    n = read();
    ll left = 1, right = n;
    ll ansi = 0, ansj = 0, ansk = 0;

    // 先找出k
    // tot为询问的逆序对个数,先询问到总的
    ll tot = query(left, right);

    while (left + 1 < right)
    {
        ll mid = left + ((right - left) >> 1);

        // 等于的话证明mid后面的元素都是有序的,不受影响
        if (tot == query(1ll, mid))
            right = mid;
        else
            left = mid;
    }

    // 此时left即为第一个能令query==tot的元素下标
    // 否则就是right
    if (query(1ll, left) == tot)
        ansk = left;
    else
        ansk = right;

    // 现在找j

    ll rightsize = tot - query(1ll, ansk - 1);

    ansj = ansk - rightsize;

    // 找i

    // 由sum1到n=(1+n)*n/2公式得
    ll leftsize = sqrt(query(1ll, ansj - 1) * 2ll)+1;

    ansi = ansj - leftsize;

    print(ansi, ansj, ansk);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    t = read();
    while (t--)
        solve();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值