题解 [LuoguP8320]『JROI-4』Sunset

题解 P8320 『JROI-4』Sunset

link

发现第二种操作“使 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} d1mid,d1n 中分别有几个不同的值。如果二值相同,则肯定后半段的所有值都没有左半段最大值大;如果二值不同,则右半段肯定有一个数更新了 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} d1mid,d1r 进行比对即可。

可以使用记忆化减少一些查询次数。

//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;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值