像潮落潮涌,送我奔向自由。|

寂静的海底

园龄:3年2个月粉丝:59关注:15

[题解]CF1589D

题意:

sb交互题 。。。

有一个n个数的排列{ai}n109级,初始ai=i,(测试者)选出一个三元组 (i,j,k),并对[i,j1][j,k]分别倒序。

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


分析以及思维过程:

首先看到数据范围为 109 级别,询问次数不超过40,故考虑 logn 级别的算法。

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

第一反应:

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

二分出左端点、右端点,即可求出中间点,区间长度为a,b那么a×(a1)/2+b×(b1)/2=ask(1,n)

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

因为二分左端点和右端点的时间复杂度都是log2109的,所以总查找次数会达到30×2=60

所以我们只能二分一次。

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

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

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

{x,x1,x2,,1}

我们在此之前加一个数x+1 那么逆序对数会增加x{x+1,x,x1,x2,,1}

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

可以推出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 @   寂静的海底  阅读(3)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起