题目链接
题意:
交互题。
有一段长度为 n 的原始排列数组 (a[i] = i) 被 i,j,k
三个数字控制所翻转:
区间 [i,j-1]
翻转,区间 [j,k]
翻转。(区间翻转:首位置和末位置互换,次位置和末次位置互换…)
对系统最多可以有40次询问,每次询问输入 l,r
,返回的是 [l,r]
这段区间中的逆序对数。
求处理这段数组所用的 i,j,k
。
前言:
第一次做交互题,先搞清楚玩法:
可以向系统提出一定次询问,每次系统会给出问题的答案。
需要根据这些答案,来推算出题目的答案。
一般和二进制,二分相关。
询问模板:
// 问系统params, 返回系统给你的答案
T ask(T params ...) {
cout << params << endl; // 输出你要问的问题 给系统
cout.flush(); // 清空缓存
cin >> ans;
return ans;
}
回到这道题:
思路:
举个例子:
1 2 3 4 5 6 7 8 9 10 11
————— ——————————
1 2 5 4 3 10 9 8 7 6 11
————— ——————————
将区间 [3,5], [6,10]
翻转,对应的 i = 3, j = 6, k = 10。
现在要经过不超过40次询问来得到这三个数,每次询问得到询问区间中的逆序对数。
因为数组初始为原始排列,所以初始是没有逆序对数的。
所以可以二分询问 [1,mid]
这段区间的逆序对数,如果不为0,说明 mid 点在翻转区间中,即在点 i 右边。如此,便可以得到第一个数 i。区间长度在 int 范围内,所以最多询问32次。
接下来询问 [i,n]
和 [i+1,n]
区间中的逆序对数。
如例,[i,n]
中的逆序对数也为 [3,5] 中的逆序对数,即 2+1,[i+1,n]
的逆序对数也为[4,5] 中的逆序对数,即1。那么,两个区间逆序对数之差+1便为第一段翻转区间的长度。得到第二个数 j。
类似,询问 [j,n]
和 [j+1,n]
区间中的逆序对数。
如例,[j,n]
中逆序对数 4+3+2+1,[j+1,n]
中逆序对数 3+2+1,两者之差便是第二段区间的长度。遂得到第三个数 k。
原理:因为翻转过的区间为递减区间,所以一个位置到末尾的逆序对数为下一位置到末尾的逆序对数+区间长度-1。
一共询问 32+2+2=36 次。
Code:
const int N = 100010, mod = 1e9+7;
int T, n, m;
int a[N];
ll ask(int l,int r)
{
cout<<"? "<<l<<" "<<r<<"\n";
cout.flush();
ll ans; cin>>ans;
return ans;
}
bool check(int mid)
{
if(ask(1,mid)) return 1;
return 0;
}
int main(){
cin>>T;
while(T--)
{
cin>>n;
int l=1,r=n;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
int i = l-1;
int j = i+ask(i,n)-ask(i+1,n)+1;
int k = j+ask(j,n)-ask(j+1,n);
cout<<"! "<<i<<" "<<j<<" "<<k<<"\n";
}
return 0;
}
第一次做交互题,还不错。