CF949E Binary Cards 题解
妙妙题。
为了方便叙述,称取出的 card 集合为 \(S\)。
我们的做法基于以下几点观察:
-
\(S\) 为不重集。
证明:若存在两个 \(2^k\) 则 \(2^k\) 与 \(2^{k+1}\) 一定不劣。
-
对于 $\forall k \ge 0, 2^k \in S \Longrightarrow -2^k \notin S, -2^k \in S \Longrightarrow 2^k \notin S $。
证明:若存在 \(2^k\) 与 \(-2^k\) 则取 \(2^k\) 与 \(-2^{k+1}\) 一定不劣。
也就是说对于一个 \(k\) 只可能从 \(\left\{2^k,-2^k\right\}\) 中选一个。
由此我们得到了一个 \(\mathcal O(n^2)\) 的算法:
考虑枚举要拼出的数(我们称这个集合为 \(T\))的二进制位从低向高搜索,搜索时,若该位全为 \(0\) 则直接进入下一位的搜索。
否则枚举该位添 \(2^k\) 还是 \(-2^k\),将对应位为 \(1\) 的加上这个值并向下递归。
但是我们发现一个问题,如果加上对应值后变成了可重集则将其去重显然是合理的,而且还能优化常数。
优化常数?
接下来我们将证明它是 \(\mathcal O(n\log n)\) 的。
我们发现对于每一次递归,我们的时间复杂度都是 \(\mathcal O(|T|)\) 加上两侧递归的复杂度。
但是注意到在第 \(i\) 层时,\(T\) 中所有数的最后 \(i\) 位均为 \(0\)。
这限制了,在第 \(i\) 层,\(\mathcal O(T)= \mathcal O(2^{k - i})\)。(\(k\) 为总层数)
而 \(O(k)=O(\log_2 n)\),所以事实上总复杂度为 \(\mathcal O(n \log n)\)。
代码实现:
先将 \(T\) 排序。
一个递归,在全局维护集合 \(T\),并在每次进入递归前存副本,枚举该位后将 \(1\) 消掉并使用 unique
去重,进入递归。
注意,不能每次 sort
再 unique
,这样时间复杂度变为 \(T(n)=\mathcal O(n \log n) + 2T(\cfrac{n}{2})=\mathcal O(n\log^2 n)\)。
为什么每次只用 unique
而不用排序?
因为每次加上对应的值后其相对位置并不会改变。
constexpr int MAXN = 1e5 + 5;
int n, realn[20];
int a[MAXN], backup[20][MAXN];
vi solve (int dep) {
if (dep >= 20)
return vi(0);
bool flg = true;
rep (i, 1, n)
if (a[i] & 1)
flg = false;
if (flg) {
rep (i, 1, n)
a[i] >>= 1;
return solve(dep + 1);
}
rep (i, 1, n)
backup[dep][i] = a[i];
realn[dep] = n;
rep (i, 1, n) {
if (a[i] & 1)
a[i]++;
a[i] >>= 1;
}
n = unique(a + 1, a + n + 1) - a - 1;
vi ans = solve(dep + 1);
n = realn[dep];
rep (i, 1, n)
a[i] = backup[dep][i];
rep (i, 1, n) {
if (a[i] & 1)
a[i]--;
a[i] >>= 1;
}
n = unique(a + 1, a + n + 1) - a - 1;
vi ans2 = solve(dep + 1);
n = realn[dep];
rep (i, 1, n)
a[i] = backup[dep][i];
ans.pb(-(1 << dep));
ans2.pb(1 << dep);
if (sz(ans) > sz(ans2))
return ans2;
return ans;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
rep (i, 1, n)
cin >> a[i];
sort(a + 1, a + n + 1);
vi ans = solve(0);
cout << sz(ans) << endl;
for (int i : ans)
cout << i << " ";
cout << endl;
return 0;
}