Loading

【题解】[FARIO2013]Torusia

通信题,小 A 和小 B 迷失在 \(4096\times 4096\) 的方阵中。
方阵是循环的,比如 \((0,4095)\) 的右边是 \((0,0)\),上面是 \((4095,4095)\)。两人都不知道自己的绝对位置。每一秒钟小 A 可以在他坐标系下的某个点打上标记,然后小 B 询问在他坐标系下 一行一列 中标记个数。你需要尽快让小 B 知道在他坐标系下小 A 的坐标,所花时间为 \(x\),得分为 \(\min\{100,10 + \dfrac{13000}{x}\}\)

\(x = 144\) 即可满分。

我们先考虑怎么求行,\(4096 = 64^2\),小 A 先将所有 \((64p, 64q)\) 的点打上标记。小 B 询问 \(0\sim 63\) 行/列即可知道最终坐标为 \((64p + a, 64q + b)\) 中的 \(a,b\)。相当于转化为 \(64 \times 64\) 的子问题。这个方法大概需要 \(256\) 秒。

开始填充标记需要 \(64\) 次,找到 \(a,b\) 各需要 \(64\) 次,我们可以将 填充和寻找放到一起,记得将询问和标记随机,这样期望在 \(128\) 次之内就找到。

但是对于 \(64\times 64\) 的子问题不可能用相同的方法。因为我们只剩最后 \(16\) 次操作可以进行。这恰好是一个倍增的复杂度。我们构造标记,使得 \(p,q\ge 32\) 的行/列,恰好每行/列 \(1\) 个标记,\(\ge16\) 的恰好 \(2\) 个标记,依此类推。我们要求的是第 \(0\) 行的坐标,这样每次询问当前行的标记数量,可以将规模减半,比如标记数量为 \(2\),说明 \(16\le x<32\),我们直接将 \(x - 16\)

因为这个网站没有配置好通信题,所以下面代码的通信方式有点大病(

应该还有优化 / 更好的实现方法。下面这份代码代码分数在 \(99\) 附近波动。

下面是小 A 的代码

void alison(){
	for(int i = 0; i < 4096; i += 64)mark(i, i);
	int s = 32;
	while(s > 1){
		int p = s / 2;
		rep(i, 0, s - 1)mark(p * 64, i * 64), p = (p + 1) % s;
		s = p;
	}
}

下面是小 B 的代码

int p[100], q[100];
std::mt19937 rd(7274482);
inline void su(int &x,int y){
	x -= y; if(x < 0)x += 4096;
}
void bill(){
	rep(i, 0, 63)p[i] = i + (rd() & 63) * 64, q[i] = i + (rd() & 63) * 64;
	shuffle(p, p + 64, rd), shuffle(q, q + 64, rd);
	int x = 0, y = 0, t = 0;
	while(true){
		++t; 
		if(numRow(p[t & 63])){x = p[t & 63]; break;}
	}
	while(true){
		++t;
		if(numColumn(q[t & 63])){y = q[t & 63]; break;}
	}
	while(t < 127)numRow(0), t++;
	while(true){
		int w = numRow(x);
		if(w == 6)break;
		su(x, (1 << (5 - w)) * 64); 
	}
	if(numRow((x + 64) & 4095) != 6)su(x, 64);
	while(true){
		int w = numColumn(y);
		if(w == 6)break;
		su(y, (1 << (5 - w)) * 64);
	}
	if(numColumn((y + 64) & 4095) != 6)su(y, 64);
	found(y, x);
}
posted @ 2022-08-19 15:11  7KByte  阅读(84)  评论(0编辑  收藏  举报