P5208 [WC2019] I 君的商店 题解

第一道黑题,发个题解。

很好玩的一道交互题。

题意

  • 有一个长为 \(n\) 的 01 字符串,保证至少有一个 1,且已知 1 的数量的奇偶性。

  • 每次可以询问两个下标集合,返回哪个下标集合中 1 的个数更多(相同则可能返回其中任意的一个)。

  • 求该字符串,查询次数有限。

题解

我们约定 a,b,c 等字母表示下标,\(ans_a\) 表示 \(a\) 处的值。

Subtask 3

我们首先注意到这个特殊的 subtask 3:\(N\leq 10^5,M=100\),保证序列单调(不降或不增)。

这意味着这个序列一定是形如 0000…1111 或者 1111…0000 这样的形式。

我们很自然的想到可以二分出 01 的分界点,可是该如何二分呢?

首先虽然原题说可以让我们查询任意长度的数列,但是只有以下两种查询有用,我们把它记作 query1query2

  • query1(a,b) : 只查询 ab 两个位置的量(query({a},1,{b},1) ) ,这样可以确定 ab 之间的相对大小关系(若返回 0 ,则 \(ans_a\ge ans_b\) ; 否则 \(ans_a\le ans_b\) ) ;
  • query2(a,b,c) :查询两个数{a,b}c 的关系(query({a,b},2,{c},1)),这样可以确定 {a,b} 中有多少个 1。

既然序列保证单调并且一定至少有一个 1,那么我们就可以先进行一次 query1(0,n-1) 判断序列是单调不增还是不降(若返回 0 ,那么一定单调不增(为 1111…0000 形式);否则一定单调不降(为0000…1111 形式))。为了叙述方便,我们认为它单调不增。

接下来确定了 1 的位置,我们就可以愉快的二分了!我们认为 l 是最右且必定为 1 的位置,r 为最左且必定为 0 的位置 -2(否则会死循环)。

now 为必定为 1 的位置,进行query2(mid,mid+1,now) ,如果返回 1 ,那么 \(ans_{mid},ans_{mid+1}\) 中至多有一个为 1,又因为单调不增,所以 \(ans_{mid+1}\) 一定为 0。如果返回 0,那么\(ans_{mid},ans_{mid+1}\) 中至少有一个为 1,又因为单调不增,所以 \(ans_{mid}\) 一定为 1

这样我们可以二分出除了最中间的分界点以外的所有点,只有 \(ans_{l+1}\) 不知道。这时通过题目给的我们的奇偶性判断即可。

二分每次消耗 3 个体力,一开始消耗 2 点,最多消耗体力值为\(2+3\times\log_2 10^5 \approx 52 < 100\) ,可以通过。

Code:(这个代码不好写,调试技巧)

#include<bit/stdc++.h>

int query(int *S, int nS, int *T, int nT);
int query1(int a, int b) {
	int tmp1[1] = {a}, tmp2[1] = {b};
	return query(tmp1, 1, tmp2, 1);
}
int query2(int a, int b, int c) {
	int tmp1[2] = {a, b}, tmp2[1] = {c};
	return query(tmp1, 2, tmp2, 1);
}

vector<int>vec;
void find_price(int task_id, int n, int k, int ans[]) {
	vec.clear();
	if (task_id == 3||n<=2) {
		int bigger = query1(0, n - 1);
		int now = bigger ? (n - 1) : 0;
		ans[now] = 1;
		for (int i = 0; i < n; i++) vec.push_back(i);
		if (now)
			reverse(vec.begin(), vec.end()); // 从大到小 111...000
		int l = 0, r = n - 1;
		while (l < r) { // 3*lg(1e5)=50
			int mid = (l + r + 1) >> 1;
			if (mid + 1 < n && query2(vec[mid], vec[mid + 1], now) ==
				1) { // 最少 1 个为 1 因为单调不增,所以ansmid=1
				r = mid - 1;              // 最左且必定为 0 的位置-2
			} else
				l = mid; // l 表示最右且必定为 1 的位置
			// cout<<l<<" "<<r<<endl;
		}
		for (int i = 0; i <= l; i++)
			ans[vec[i]] = 1;
		ans[vec[l + 1]] = (l&1)==k ? 0 : 1;
		// for(int i=0;i<n;i++)cout<<ans[i]<<" ";
		return;
	}
}

Subtask 1 2 4 5

首先我们发现对于两个数 x,y (进行一次 query1 保证 \(ans_x\le ans_y\)),把他们与 1 比较(执行query2({x,y},2,{1},1)),如果返回 0 ,那么 \(ans_y=1\) ;否则 \(ans_x=0\) 。这样我们每次花 5 个代价就可以确定一位。

证明显然。

所以我们就可以先找到 1 个 1 , 然后执行上面的操作直到只剩 1 个数,用奇偶性判断即可。

那么该如何找到 1 呢?

我们可以考虑二分的思想,每次查询 \([l,mid]\)\([mid+1,r]\) 哪边 1 多,因为至少有一个 1,所以最后一定能找到。代价约为 \(2n\)

总代价为 \(5n+2n=7n\)

Subtask 6

我们可以优化找 1 的过程:具体的说,我们不需要一开始找到一个 1 , 随便找一个数 m 进行上面的操作:

对于两个数 x,y (进行一次 query1 保证 \(ans_x\le ans_y\)),把他们与 m 比较(执行query2({x,y},2,{m},1)),如果返回 1 ,那么 \(ans_x=0\) ;否则 : 若 \(ans_m=1\), 则 \(ans_y\) 一定等于 \(1\),所以一定有 \(ans_y \ge ans_m\)

我们用 y 替换 m,记录这个操作,并继续进行。

最终我们会得到一堆 0 和一条链,记录了这些数相对的大小情况。另外还剩下 1 个数,和链的最大值比较一次得到一个 1,最后在链上按照 Subtask 3 的规则二分即可。

总代价 \(5n+\mathcal O(\log_2{n})\)

Code:AC记录

// WC2019 I君的游戏
#include <bits/stdc++.h>
using namespace std;
int query(int *S, int nS, int *T, int nT);
int query1(int a, int b) {
	int tmp1[1] = {a}, tmp2[1] = {b};
	return query(tmp1, 1, tmp2, 1);
}
int query2(int a, int b, int c) {
	int tmp1[2] = {a, b}, tmp2[1] = {c};
	return query(tmp1, 2, tmp2, 1);
}
#define MAXX 1145141919
vector<int> vec;
void find_price(int task_id, int n, int k, int ans[]) {
	vec.clear();
	if (task_id == 3||n<=2) {
		int bigger = query1(0, n - 1);
		int now = bigger ? (n - 1) : 0;
		ans[now] = 1;
		for (int i = 0; i < n; i++) vec.push_back(i);
		if (now)reverse(vec.begin(), vec.end());
		int l = 0, r = n - 1;
		while (l < r) { 
			int mid = (l + r + 1) >> 1;
			if (mid + 1 < n && query2(vec[mid], vec[mid + 1], now) ==
				1) { 
				r = mid - 1;              
			} else
				l = mid; 
		}
		for (int i = 0; i <= l; i++)ans[vec[i]] = 1;
		ans[vec[l + 1]] = (l + k ^ 1) & 1;
		return;
	}
	int z = 0, t, x = 1, y, s;
	for (int i = 2; i < n; i++) {
		y = i;
		t = query1(x, y);
		if (!t) swap(x, y);
		t = query2(x, y, z);
		if (t) {
			ans[x] = 0;
			x = y;
		} else {
			vec.push_back(z);
			z = y;
		}
	}
	t = query1(x, z);if (!t)swap(x, z);
	ans[z] = 1;
	if (!vec.size()) {
		ans[x] = k ^ 1;
		return;
	}
	vec.push_back(z);
	reverse(vec.begin(), vec.end()); 
	int nnnn = vec.size();
	int l = 0, r = nnnn - 1;
	while (l < r) { 
		int mid = (l + r + 1) >> 1;
		if (mid + 1 < nnnn && query2(vec[mid], vec[mid + 1], z) ==
			0) { 
			l=mid ;                
		} else
			r=mid-1;
	}
	for (int i = 0; i <= l; i++)
		ans[vec[i]] = 1;
	int c = vec[l + 1];
	if(l+1==vec.size())goto AAA;//可能会越界
	if (query1(c, x))
		swap(c, x);
	if (!query2(c, x, z)) {
		ans[c] = 1;
		l++;
	} else {
		ans[x] = 0;
		x = c;
	}
AAA:	ans[x] = (l + 1 ^ k) & 1;
	return;
}

完结撒花!

posted @ 2024-01-29 19:31  wangyishan  阅读(11)  评论(0编辑  收藏  举报