CF1386A-Colors——二分思维题

本文介绍了一道关于通过64次询问找出[1,N]范围内值CCC的难题,利用二分法和跳跃策略避免重复询问。核心思路是证明每次跳跃确保不重复,并确定初始询问点以适应任意CCC值。通过实例代码展示了如何找到合适起点并进行有效询问。
摘要由CSDN通过智能技术生成

A - Colors

题目描述

这是一道交互题。给定范围 [ 1 , N ] [1,N] [1,N] ,需要你通过不超过64次询问问出该范围内的一个值 C C C 。每次询问需要你给出范围 [ 1 , N ] [1,N] [1,N] 内的一个数,然后评测机返回1/0表示你这次询问与上次询问给定数的差值是否大于等于 C C C每个数只能被询问一次

数据范围与提示

共有 T T T 组数据。

1 ≤ n ≤ 1 0 18 1\le n\le 10^{18} 1n1018 , 1 ≤ T ≤ 100 1\le T\le 100 1T100

思路

因为 log ⁡ 2 1 0 18 ≈ 64 \log_210^{18} \approx 64 log2101864 ,想到二分法。然后再想想,这题除了用二分法询问没有其它办法,所以这题就是一个二分的升档题。

但是这题限制有点多,只能从前一处往后一处跳一定距离来询问,而且不能跳重复的点。 二分的题肯定不会太复杂,所以直觉告诉我们,肯定有不用 map \text{map} map 的方法把第二个限制解决掉。

有一个很重要的结论用来省掉这个限制:如果我们每次二分出一个距离,然后从上一次的点往相反方向跳,也就是不断来回跳,则一定不会跳到重复的点。

证明:倒数第 i i i 次二分询问的值与倒数第 i + 1 i+1 i+1 次的差为 2 i − 1 2^{i-1} 2i1 ,那么如果出现了重复的值,必定是有一段坐标的+、-、+、-的和等于零。把第1个、第2个作差,第3个、第4个作差,一直这么作差下去,就等于 2 k ± 2 k − 1 ± 2 k − 2 ± . . . 2^{k}\pm 2^{k-1}\pm 2^{k-2}\pm ... 2k±2k1±2k2±... ,根据我们学过的倍增的知识很容易判断:这个式子的绝对值最小为1,也就是不可能出现和为零,所以不会跳到重复的坐标。

这个性质对于范围为2的次幂的情况显然是成立的,但是对于普通的二分呢?我们只需要让每次缩小过后的范围不大于上一次范围的一半下取整即可,也就是每次让 r = m i d − 1 r=mid-1 r=mid1 l = m i d + 1 l=mid+1 l=mid+1

显然,来回跳这种方法是期望下左右跨度最小的方案,而这个跨度不可能超过 N N N

剩下的问题是,怎么找到一个起始点,让我们无论怎么二分都不会超出 [ 1 , N ] [1,N] [1,N] ?其实稍微想一想就知道,对于每一个 N N N ,一定存在某一个起始点,使得任意一个 C C C 值都可以用不超范围的询问得到。不然由于你无法预测 C C C 值,这题就没法做。官方题解里说得很含糊,但大概也是这个意思,所以出题人心理学可以帮你省掉很多不必要的证明

然后剩下任务的就只剩找到一个合适的起点了。首先考虑答案为 N N N ,那么你的二分过程是 n − 2 m , n − 2 m − 1 , n − 2 m − 2 , . . . n-2^{m},n-2^{m-1},n-2^{m-2},... n2m,n2m1,n2m2,... 然后来回跳,根据等比数列的知识很容易算出第一次往右跳的起始点在 n 3 \frac{n}{3} 3n ,所以我们要求的起始点就在 n 3 \frac{n}{3} 3n 左右,暴力枚举一下就好了。判断过程也很简单,设 C = N C=N C=N 然后走一遍二分看超不超边界就行。为了保险起见,你还可以再设 C = 1 C=1 C=1 然后走一遍。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#define ll long long
#define MAXN 200005
#define uns unsigned
#define INF 0x3f3f3f3f
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
ll n,p;
inline bool OK(ll bg){
	if(bg>n||bg<1)return 0;
	ll l=1,r=n-1,mid;bool t=1;
	while(l<=r){
		mid=(l+r)>>1;
		if(t)bg+=mid;
		else bg-=mid;
		if(bg>n||bg<1)return 0;
		l=mid+1,t^=1;
	}
	return 1;
}
inline bool check(ll m){
	printf("? %lld\n",m);
	cout.flush();
	return read();
}
int main()
{
	for(int T=read();T--;){
		n=read();
		ll p=n/3;
		for(int i=0;i<1000;i++){
			if(OK(n/3+i))p=n/3+i,i=MAXN;
			else if(OK(n/3-i))p=n/3-i,i=MAXN;
		}
		ll l=1,r=n-1,mid;
		bool t=1;
		check(p);
		while(l<=r){
			mid=(l+r)>>1;
			if(t){
				bool ok=check(p+mid);
				if(ok)r=mid-1;
				else l=mid+1;
				p+=mid;
			}
			else{
				bool ok=check(p-mid);
				if(ok)r=mid-1;
				else l=mid+1;
				p-=mid;
			}
			t^=1;
		}
		printf("= %lld\n",l);
		cout.flush();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值