题目
一开始有一个有序序列
1 2 3 4 5 …n
因为一次操作,它有两个部分被反转了。
i 到 j-1 j到k
这样使得它产生了一些逆序数。
你可以询问l r 内有多少逆序数。
你可以至多进行40次 询问,要求在询问后判断有原序列的 i j k 是什么值。
3<=n<=1e9
题解思路
因为1e9内要二分n需要至多30次,所以我们只有剩下的几次来发现答案。
我们可以先二分出i 或者 k 。这两个点的性质肯定是逆序数总和刚好改变的点。所以我们需要先求逆序数的总和。就可以确定 i k中的一个点。
再观察这个式子。
3 2 1
4 3 2 1
5 4 3 2 1
后面最大的数贡献的逆序数刚好是它本身减1。
这样我们就可以在两步确定一段区间的长度了。
这样这题就解决了。
AC代码
#include <bits/stdc++.h>
//#include <unordered_map>
//priority_queue
#define PII pair<int,int>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
int tonum(string s )
{
int sz = s.size() ;
int k = 0 ;
for (int i = 0 ; i < sz ; i++ )
{
k *= 10 ;
k += s[i] - '0' ;
}
return k ;
}
int ask(int l , int r )
{
string ch ;
cout << "? " << l << " " << r <<endl;
cin >> ch ;
return tonum(ch) ;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T ;
cin >> T ;
while(T--)
{
int n ;
cin >> n ;
int l = 2 , r = n -3 ;
int tot = ask(1,n) ;
while (l <= r )
{
int mid = l + r >> 1 ;
if (ask(mid,n) == tot )
{
l = mid + 1 ;
}else
r = mid - 1 ;
}
int i = r ;
int j = i + tot - ask(r+1,n) + 1 ;
int k = j + ask(j,n) - ask(j+1,n) ;
cout << "! " << i << " " << j << " " << k <<endl;
}
return 0 ;
}