CF949E Binary Cards 题解

CF949E

妙妙题。

为了方便叙述,称取出的 card 集合为 \(S\)

我们的做法基于以下几点观察:

  1. \(S\) 为不重集。

    证明:若存在两个 \(2^k\)\(2^k\)\(2^{k+1}\) 一定不劣。

  2. 对于 $\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 去重,进入递归。

注意,不能每次 sortunique,这样时间复杂度变为 \(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;
}
posted @ 2025-02-21 21:18  LightningCreeper  阅读(3)  评论(0编辑  收藏  举报