题解 P8320 『JROI-4』Sunset
发现第二种操作“使 a i a_i ai 为 0 0 0”,一旦操作了就不能再反悔,我们就永远失去了对这个数字原本的值的所有信息。因此这个操作的作用只有:在计算完这个数字的值后,消除这个数字对其他数字的影响,转而计算其他数字的值。
因此我们考虑每次计算出序列最大值,然后把该最大值变为 0 0 0,之前的次大值就变成了现在的最大值,计算 n n n 次即可还原整个序列。
看到 n n n 最大为 500 500 500,操作次数最大为 5500 5500 5500,所以每次计算最大值最多只有 11 11 11 次机会,考虑一种 log n \log n logn 做法:二分。
对于区间 [ 1 , n ] [1,n] [1,n],进行一个拆分: [ 1 , m i d ] , [ m i d + 1 , n ] [1,mid],[mid+1,n] [1,mid],[mid+1,n]。如何判断最大值在左边还是在右边呢?很简单:操作一查询 d 1 ∼ m i d , d 1 ∼ n d_{1\sim mid},d_{1\sim n} d1∼mid,d1∼n 中分别有几个不同的值。如果二值相同,则肯定后半段的所有值都没有左半段最大值大;如果二值不同,则右半段肯定有一个数更新了 d d d 值,即比左半段所有数都大。
若是区间 [ l , r ] [l,r] [l,r] 呢?也一样,查询 d 1 ∼ m i d , d 1 ∼ r d_{1\sim mid},d_{1\sim r} d1∼mid,d1∼r 进行比对即可。
可以使用记忆化减少一些查询次数。
//C
//这是一道IO交互题
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
vector<int> v;
int ans[N];
int ask(int x){
if(x == 1) return 1;
printf("? 1 %d\n", x);
fflush(stdout);
int t; scanf("%d", &t);
return t;
}
int ra;
int getmx(int l, int r){
if(l == r) return l;
int mid = l + r >> 1;
int t = ra, tt = ask(mid);
if(t == tt){
ra = tt;
return getmx(l, mid);
} else {
ra = t;
return getmx(mid+1, r);
}
}
int main(){
int t,n; scanf("%d", &t);
while(t--){
scanf("%d", &n);
for(int i = n; i; -- i){
ra = ask(n);
int k = getmx(1, n);
ans[k] = i;
printf("? 2 %d\n", k);
fflush(stdout);
}
printf("!");
for(int i = 1; i <= n; ++ i) printf(" %d", ans[i]); puts("");
fflush(stdout);
}
return 0;
}