Codeforces Round 922 (Div. 2) E. ace5 and Task Order 交互、分治

题目链接

题目大意

        有一个 n (1<=n<=2000) 个数的排列 a 和一个数 x,排列 a 和 x 全部未知,每次你可以询问下标为 i 的数a[i] 与 x 的大小关系。若该数大于 x 则回答 ' > ' 并且x 增加一;若该数小于 x 则回答'<' 并且 x 减去一;若该数等于 x 则回答 '=' 并且 x 不变。要求在 40n 次操作内确定出排列 a 。

思路

        可以发现,若一直询问一个数,若该数大于x则x++,若该数小于x则x--,x将不断的逼近该数,直至出现’=‘,x等于该数。故,要将x变成指定的一个数最多需要n次询问。

        当x的值等于某个数之后,让其与另一个数 y 相比,则可得到y与这个数的大小关系。再询问一次原来的那个数,x又恢复为原来的值。询问完大小之后再恢复回来需要两次操作。

        故将一个数与其他所有的数比较大小需要2n次询问,加上变为指定数的n次,一共需要3n次询问。故可用3n次操作将大于和小于的数分开,类似于快速排序。每次选择集合内的某一个数,并与其他数比较,分为大于和小于两个部分,再分别递归处理。

        故要跑\log_{2}n次的指定相等和分大小,最多可以跑40n次,每次指定相等和分大小需要3n次,n<=2000, \log_{2}n约等于11,40/3约等于13。

code

#include<bits/stdc++.h>
#include<random>
#define int long long
using namespace std;
const int N = 2010;
int a[N];
mt19937 rnd(time(0));
//产生同unsigned int类型取值范围的随机数

//将x变为与第pos位的数相等
void hd(int pos) {
	char ch;
	while (1) {
		cout << "? " << pos << '\n';
		cout.flush();
		cin >> ch;
		if (ch == '=')
			break;
	}
}

void srt(int l, int r, vector<int>v) {
	if (v.empty()) return;
	if (l > r) return;
	if (l == r) {
		//当区间长度为1时,可确定v中所存编号所对应的数的值,即为区间端点值
		for (auto k : v)
			a[k] = l;
		return;
	}
	int tmp = v[rnd() % v.size()];
	//随机取一个编号,不要指定,指定的话在极端情况下会tle

	hd(tmp);//让x与指定的这个编号所对应的数相等

	char ch;
	vector<int>s, f;//分别存大于和小于的数

	for (auto k : v) {
		if (k == tmp) continue;//查到选中的编号时,直接continue,询问无用

		cout << "? " << k << '\n';
		cout.flush();
		cin >> ch;
		//询问k位置上的数与指定的数的大小关系

		if (ch == '>')
			s.push_back(k);
		else
			f.push_back(k);
		//将大于和小于的数分开

		cout << "? " << tmp << '\n';
		cout.flush();
		cin >> ch;
		//与指定位置的数相比,x恢复为指定位置的数
		//这样才能继续判断下一位数与指定位置的数的大小关系,
		//直至判断完所有数,把所有数分为大于和小于两部分
	}
	int p = r - s.size();
	//求指定的tmp位置对应的数是谁
	//r指的是目前排序的这个区间里最大的值,而动态数组s存大于tmp位置的数
	//又因为目前排序的这个区间大小是连续的,
	//所以最大值减去大于他的数的个数即为tmp位置所对应的值,存入a[]中作为最终答案输出

	a[tmp] = p;
	srt(p + 1, r, s);
	srt(1, p - 1, f);
	//递归处理大于和小于部分,注意每一部分对应的值都是连续的
	//因为a本身是一个排列,与一个数同时比大小,也会被分成连续的大于和小于两部分
	//递归直至区间长度为1,确定每一个位置所对应的值
}
void solve() {
	int n = 1;
	cin >> n;
	vector<int>v;
	for (int i = 1; i <= n; ++i) {
		a[i] = 0;
		v.push_back(i);//v存的是编号
	}
	srt(1, n, v);
	cout << "! ";
	for (int i = 1; i <= n; ++i)
		cout << a[i] << " ";
	cout << '\n';
	cout.flush();//注意每一个cout都要这一句,刷新输出缓冲区
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) solve();
	return 0;
}

        

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值