[题解]CF1589D

题意:

sb交互题 。。。

有一个$n$个数的排列$\{a_i\}$,$n$为$10^9$级,初始$a_i=i$,(测试者)选出一个三元组 $(i,j,k)$,并对$[i,j-1][j,k]$分别倒序。

你可以询问区间$[l,r]$的逆序对数,至多不超过40次,推测并回答出三元组$(i,j,k)$。


分析以及思维过程:

首先看到数据范围为 $10^9$ 级别,询问次数不超过40,故考虑 $\log n $ 级别的算法。

为方便书写 $ask(l,r)$ 表示询问$[l,r]$的逆序对数。

第一反应:

考虑二分,首先 $ask(1,n)$ ,然后对于区间$[1,n]$查找左端点,如果$ask(mid,n)$不等于$ask(1,n)$那么左端点就在$[1,mid-1]$,因为逆序对减少了,反之则在$[mid+1,n]$

二分出左端点、右端点,即可求出中间点,区间长度为$a,b$那么$a\times (a-1)/2+b\times (b-1)/2=ask(1,n)$。

交上去才发现询问次数过多……

因为二分左端点和右端点的时间复杂度都是$\log_2 10^9$的,所以总查找次数会达到$30\times2=60$次

所以我们只能二分一次。

先二分左端点$i$,并推出$j,k$

利用好这个排列的特征 , 它本身单调递增,那么颠倒的部分就是单调递减的 。

如果有一个单调递减的序列

$\{x , x-1,x-2,……,1\}$

我们在此之前加一个数$x+1$ 那么逆序对数会增加$x$组$\{x+1,x , x-1,x-2,……,1\}$

利用这个性质,我们询问$ask(i,n),ask(i+1,n)$这之差就是$i$造成的逆序对数,既$j+1-i$

可以推出$j$

推出$j$后同理$ask(j,n),ask(j+1,n)$即可求出$k$


ACcode:

C++

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[102],b[102],n,t,l,r,zs,i,j,k;
bool check(int a,int b)
{
    cout<<'?'<<' '<<a<<' '<<b<<endl;
    cout.flush();
    int p;
    cin>>p;
    if(p==zs) return 1;
    return 0;
}
int asknxd(int l,int r)
{
    cout<<'?'<<' '<<l<<' '<<r<<endl;
    cout.flush();
    int p;
    cin>>p;
    return p;
}
signed main()
{
    cin>>t;
    while(t--)
    {
        cin>>n;
        cout<<'?'<<' '<<1<<' '<<n<<endl;
        cout.flush();//因为这行痛失D题
        cin>>zs;
        l=1;r=n;
        int ans = -1;
        while(l<=r) 
        {
            int mid = l+r>>1;
            if(check(mid,n))  ans = mid ,l=mid + 1;
            else r=mid-1;
        }    
        i= ans ;
        int nxd1 =asknxd(i,n);
        int nxd2 =asknxd(i+1,n);
        j=i+(nxd1-nxd2)+1;
        nxd1 =asknxd(j,n) ;
        nxd2 =asknxd(j+1,n) ;
        k=j+(nxd1-nxd2);
        cout<<'!'<<' '<<i<<' '<<j<<' '<<k<<endl;
        cout.flush();
    }
}

很不错的一道题,考完后才发现非常简单

posted @ 2021-11-15 00:04  寂静的海底  阅读(1)  评论(0编辑  收藏  举报  来源