Mystery Square

给你一个完全平方数 \(A\) 的二进制表示,但是其中有 \(k\) 个位被替换为了 ?,请你找回原来的数。

\(A \le 2^{125}, k\le 40\)


下文令 \(a = \sqrt{A}\),令串的长度为 \(n\)。 下文所指的前一半和后一半均向上取整。

一个观察是 \(k\le40\),所以前一半和后一半中问号个数中的最小值 \(\le 21\)

我们可以简单地枚举小的那部分的至多 \(2^{21}\) 中方案,然后依次校验他们。

接下来我们来考虑当我们知道完整的前一半或者后一半的时候怎么找出原来的数。

前一半

我们令已经确定的数位中 \(1\) 位置上的和为 \(x\),仍未确定的数位的位置上的和为 \(y\)

不难发现 \(\sqrt{x} \le a \le \sqrt{x+y}\),同时我们有 \(x\ge2^n,y\le2^\frac n2\)

然后接下来,

\[\begin{align*} &\sqrt{x+y} - \sqrt{x} \\=&\dfrac{y}{\sqrt{x+y}+\sqrt{x}} \\\le&\dfrac{y}{\sqrt{2^n+y}+\sqrt{2^n}} \\\le&\dfrac{2^\frac n2}{\sqrt{2^n+2^\frac n2}+2^\frac n2} \\=&\dfrac{1}{\sqrt{1+\frac 1{2^\frac n2}}+1} \\\le& \ \dfrac 12 \end{align*} \]

因此我们发现确定了前一半的数之后,只有至多一个可能的 \(a\),我们只需要检查 \(\left\lfloor{\sqrt{x+y}}\right\rfloor\) 或者 \(\left\lceil{\sqrt{x}}\right\rceil\) 其中之一即可。

这样,我们就在 \(O(1)\) 的时间内仅通过前一半得出了整个数。

后一半

如果当前的 \(A\) 的后两位有可能是 00,那么我们就去掉后两位后递归处理,如果找到了解返回答案 \(\times 4\) 即可。

这样,我们就可以假定当前 \(a\) 是一个奇数。

然后我们可以通过递推的方式从低往高依次添加位,找到 \(x^2 \equiv A \pmod {2^{\lceil \frac n2 \rceil}}\) 的所有解。

可以证明对于每个 \(2^t\) 的子问题,最多有 \(4\) 个可能的解,最后依次校验即可。

这样,我们就可以在 \(O(1)\) 的时间内仅通过后一半得出整个数。


以下是证明:

我们令命题 \(P(t)\) 表示对于 \(t\ge3\) 的情况下,对于任意一个 \(y\equiv 1\pmod 8\),方程 \(x^2\equiv y \pmod {2^t}\) 存在 \(4\) 个解 \(x_0,x_1,x_2,x_3\),并且满足 \(x_1=x_0+2^{t-1},x_3=x_2+2^{t-1}\)

首先当 \(t=3\) 的时候,唯一一个满足条件的 \(y=1\),此时存在 \(4\) 个解:\(1,3,5,7\),并且有 \(5=1+2^2,7=3+2^2\),所以 \(P(3)\) 成立。

接下来,假设 \(P(t)\) 成立,我们来证明 \(P(t+1)\) 成立。

考虑基于 \(P(t)\) 时的四个解 \(x_0,x_1,x_2,x_3\) 满足 \(x_i^2\equiv y' \pmod {2^t}\) 进行扩张。

当二进制下第 \(t\) 位的取值为 \(0\) 时:

我们考察 \(x_1^2,x_3^2 \bmod 2^{t+1}\) 的结果。

\[\begin {align*} \\x_1^2=&(x_0+2^{t-1})^2=x_0^2+2^tx_0+2^{2t-2}\equiv x_0^2+2^t \pmod {2^{t+1}} \\x_3^2=&(x_2+2^{t-1})^2=x_2^2+2^tx_2+2^{2t-2}\equiv x_2^2+2^t \pmod {2^{t+1}} \end {align*} \]

可以发现,\(x_0^2,x_1^2,x_2^2,x_3^2\) 中,恰好有 \(2\) 个数 \(\equiv y' \pmod {2^{t+1}}\),剩下 \(2\) 个数 \(\equiv y'+2^t \pmod {2^{t+1}}\)

当第 \(t\) 位为 \(1\) 时:

由于

\[(x_i+2^t)^2=x_i^2+2^{t+1}x_i+2^{2t}\equiv x^2 \pmod {2^{t+1}} \]

因此这两种情况的结果是相同的。

所以不论 \(y=y'\) 或者 \(y=y'+2^t\),均存在 \(4\) 个解,并且由于最高位为 \(0\)\(1\) 的情况结果相同,所以其中两个数等于另外两个数 \(+2^t\)

所以 \(P(t+1)\) 成立。

那么证明这个有什么用呢?我们发现 \(2^t\) 中的奇数有 \(\frac {2^{t}}2\) 个,满足要求的 \(y\)\(\frac {2^{t}}8\) 个。

因此不仅仅是存在 \(4\) 个解,而是恰好存在 \(4\) 个解。

而对于 \(t=1,2\) 的情况,解的数量分别为 \(1,2\)

所以不管 \(\frac n2\) 是多少,解的数量不会超过 \(4\)


所以我们对这两种情况分别处理,即可通过!

时限 20s 只跑了 129ms 这对吗

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>

typedef __int128 i128;
#define p(i) ((i128(1))<<(i))

i128 solve(std::string s) {
	if (s.size() == 0) return -1;
	if (s.size() == 1) return s[0] == '0' ? -1 : 1;

	int L = s.length();

	i128 mask0 = 0, mask1 = 0;
	for (int i = 0; i < L; ++i)
		mask0 |= (s[i] == '0') * p(i),
		mask1 |= (s[i] == '1') * p(i);

	auto match = [ & ](i128 num) {
		return ((num | mask1) == num) && ((num & ~mask0) == num);
	};

	int h = (L + 1) / 2;
	int front_q = 0, back_q = 0;
	for (int i = 0; i < h; i++) front_q += s[i] == '?';
	for (int i = L - h; i < L; i++) back_q += s[i] == '?';

	if (front_q < back_q) {
		i128 raw0 = mask0, raw1 = mask1;
		std::basic_string<i128> res = {1};
		for (int k = 2; k <= h; ++k) {
			decltype(res) next;
			i128 mask = p(k) - 1;
			mask0 = raw0 & mask, mask1 = raw1 & mask;
			for (i128& y : res) {
				if (match(y * y)) next += y;
				y |= p(k - 1);
				if (match(y * y)) next += y;
			}
			res.swap(next);
		}
		mask0 = raw0, mask1 = raw1;
		for (auto&& Y : res)
			if (match(Y * Y))
				return Y * Y;
	} else {
		int pb = L / 2;
		i128 mask = 0, base = 0;
		for (int i = pb; i < L; i++)
			mask |= (s[i] == '?') * p(i),
			base |= (s[i] == '1') * p(i);
		i128 t = mask + 1;
		do {
			t = (t - 1) & mask;
			i128 Y = std::floor(std::sqrt((long double)(base | t)));
			if (match(Y * Y)) return Y * Y;
			++Y;
			if (match(Y * Y)) return Y * Y;
		} while (t);
	}

	if (s[0] != '1' && s[1] != '1') {
		i128 res = solve(s.substr(2));
		if (res != -1) return res * 4;
	}

	return -1;
}

int main() {
	std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
	int T;
	std::cin >> T;
	std::string s;
	for (int t = 1; t <= T; t++) {
		std::cin >> s;
		if (s[0] == '?') s[0] = '1';
		std::reverse(s.begin(), s.end());
		i128 ans = solve(s);
		std::cout << "Case #" << t << ": ";
		for (int i = s.size() - 1; ~i; --i)
			std::cout << (int)(ans >> i & 1);
		std::cout << "\n";
	}
}
posted @ 2025-09-05 18:59  CuteNess  阅读(1)  评论(0)    收藏  举报