[EGOI 2023] Guessing Game / 猜猜看

[EGOI 2023] Guessing Game / 猜猜看

下文称安娜为 A,贝蒂尔为 B,埃玛为 E。


\(K = N - 1\)

考虑 A 在第 \(i\) 次访问的房子中填入 \(i\),则 E 必然填入一个重复数字,找出并询问即可。

\(K = \dfrac{N}{2}\)

将所有房子分成左右两部分,并对每部分分别进行标号,进行分讨:

  • 若在一个部分中出现两个相同的数,则询问这两个数

  • 若两个部分的中数都两两不同,则询问 \(50000\) 出现的位置

这样做的正确性是显然的,且这种方法容易借鉴,下面再举一个例子。

\(K = \dfrac{N}{10} + 8\)

将所有房子分成 \(10\) 块,按照上面的方法做。

然而此时会出现多个极大值,无法进行询问区分。

考虑将每个块的最后一个值从 \(10000\) 开始标号,只会标到 \(10008\),此时进行分讨:

  • 若在一个部分中出现两个相同的数,则询问这两个数

  • 若每块的最大值中出现两个相同的数,则询问这两个数

由此我们可以推出,将房子分成 \(B\) 块,则 \(K = O(B + \dfrac{N}{B})\)。

进行平衡,可以得到 \(K = O(\sqrt{N})\) 的做法。

\(K = O(\log N)\)

考虑刚才我们分的是单层块,现在我们改为分多层块。

容易想到一种多层分块的数据结构,即线段树,考虑在线段树上进行操作。

上文中每块的最后一个元素具有特殊性,我们类比的找到 \(i\) 作为最后一个元素的最长线段。

假设该线段在线段树上的深度为 \(x\),则将 \(A_i\) 设为 \(x\),下面是例子:

考虑若 B 得到的序列包含 \(1\),则其必然为 C 填入的数字。同理,找不到对应节点同上。

否则我们举一个例子:

红色线段的两个子节点均被指向,则这两者必有一个被 E 的房子指向。

如此操作,我们就得到了 \(K = O(\log N)\) 的做法。

此时我们并没有用到 \(1\),所以可以将所有数整体减 \(1\)。

\(K = 7\)

已经不是人类了。

在前 \(N - 32\) 个访问到的房子上写上 \(7\),接下来我们分情况讨论。

假设 E 没有写数字 \(7\),则需要在 \(32\) 个房子中找到 E 的房子,沿用上文方法可以解决。

假设 E 写了数字 \(7\),我们考虑通过那 \(32\) 个房子的总和推算出 E 的房子编号。

考虑传输这个总合 \(S\),我们有 \(32\) 个位置,想到传输 \(2^{16}\) 以内的二进制数。

我们求出 \(S \bmod 2^{16}\),显然我们容易从剩余的点中提取出 \(S\)。

具体提取方式,我们将每位传两次,取奇数位即可。

我们会得到两种可能的 \(S \bmod N\),依次询问对应的答案即可。

现在考虑如何将 \(S\) 映射到对应的值上,发现相邻两位必有 \(5\),将其变成 \(6\) 即可。

#include<iostream>
#include<cstdio>
using namespace std;
const int MOD=65536;
int P,N;
bool flag[100010];
int idt[100010],pos[40],id_pos[40],rev[40];
struct Node{
	int l,r,depth,fa,remain,do_p;
}a[210];
void build(int id,int l,int r,int depth,int fa){
	a[id].l=l;
	a[id].r=r;
	a[id].depth=depth;
	a[id].fa=fa;
	if(l==r){
		id_pos[l]=id;
	}
	else{
		int mid=(l+r)>>1;
		build(id*2,l,mid,depth+1,id);
		build(id*2+1,mid+1,r,depth+1,id);
	}
}
void Subtask_1(){
	cout<<7<<endl;
	for(int i=1;i<=N-32;i++){
		int id;
		cin>>id;
		cout<<7<<endl;
		flag[id]=true;
	}
	int tot=0,S=0;
	for(int i=0;i<N;i++){
		if(!flag[i]){
			tot++;
			idt[i]=tot;
			pos[tot]=i;
			S+=i;
			S%=MOD;
		}
	}
	for(int i=1;i<=32;i++){
		int pre=(i-1)/2;
		if(S&(1<<pre)){
			rev[i]=true;
		}
		else{
			rev[i]=false;
		}
	}
	build(1,1,32,0,0);
	for(int i=1;i<=31;i++){
		int id;
		cin>>id;
		int pos_now=idt[id];
		int pre=id_pos[pos_now],num;
		while(pre){
			a[pre].remain++;
			if(a[pre].remain==a[pre].r-a[pre].l+1){
				num=a[pre].depth;
			}
			pre=a[pre].fa;
		}
		if(num==5){
			if(rev[pos_now]){
				num=6;
			}
		}
		cout<<num<<endl;
	}
}
int A[100010];
void Subtask_2(){
	int cnt=0;
	for(int i=0;i<N;i++){
		cin>>A[i];
		if(A[i]==7){
			cnt++;
		}
	}
	if(cnt==N-31){
		int tot=0,sum=0,S=0,pre=0;
		for(int i=0;i<N;i++){
			if(A[i]!=7){
				sum+=i;
				sum%=MOD;
				if(A[i]>=5){
					if(A[i]==6){
						S+=(1<<pre);
					}
					pre++;
				}
			}
		}
		int tmp=((S-sum)%MOD+MOD)%MOD;
		int tmp2=tmp+MOD;
		if(tmp2<N){
			cout<<tmp<<" "<<tmp2<<endl;
		}
		else{
			cout<<tmp<<" "<<tmp<<endl;
		}
	}
	else{
		int tot=0;
		for(int i=0;i<N;i++){
			if(A[i]==6){
				A[i]=5;
			}
			if(A[i]!=7){
				tot++;
				idt[i]=tot;
				pos[tot]=i;
			}
		}
		build(1,1,32,0,0);
		for(int i=0;i<N;i++){
			if(A[i]!=7){
				int pos_now=idt[i];
				int pre=id_pos[pos_now];
				while(pre){
					if(a[pre].depth==A[i]){
						a[pre].do_p=i+1;
						break;
					}
					pre=a[pre].fa;
				}
				int u=a[pre].fa;
				if(a[u*2].do_p  &&  a[u*2+1].do_p){
					cout<<a[u*2].do_p-1<<" "<<a[u*2+1].do_p-1<<endl;
					return ;
				}
			}
		}
	}
} 
int main(){
	cin>>P>>N;
	if(P==1){
		Subtask_1();
	}
	else{
		Subtask_2();
	}
	return 0;
}
posted @ 2026-05-22 12:08  Oken喵~  阅读(22)  评论(0)    收藏  举报