MENU

Solution. LG10218 [省选联考 2024] 魔法手杖

Description

给你 $n$ 个 $[0,2^k)$ 中的整数 $a_1,a_2,\dots,a_n$,第 $i$ 个数对应代价 $b_i$。

你需要选取一个 $U=\{1,2,\dots,n\}$ 的子集 $S$,满足 $\sum_{i\in S} b_i\le m$,以及一个 $[0,2^k)$ 中的整数 $x$,最大化 $\min\{\min_{i\in S}\{a_i+x\},\min_{i\in U\setminus S}\{a_i\oplus x\}\}$。

共 $T$ 组数据。

$1\le n\le 10^5,1\le \sum n\le 5\times 10 ^5,0\le k\le 120,0\le m\le 10^9,0\le b_i\le 10^9$

Soluton

省选考场上已经无限接近这个题的正解,可以由于有一些细节没考虑清楚,少写了一些情况,最后挂到了 36pts,喜提暴力同分。

题目让我们将数分为加法和异或两部分,由于对一堆数进行异或的贡献很难计算,我们尝试着将这个问题放到 01-trie 上考虑。

特判掉将所有数都分入加法部分的情况。

我们希望从高往低逐位确定 $x$ 以及最终的答案。

不妨设我们当前走到了 trie 上的某个结点 $u$,已经确定的答案为 $ans$($ans$ 只有高位有值),$ans$ 应当恰好是从根走到 $u$ 的路径异或上 $x$ 对应的二进制数。

我们首先需要判断 $ans$ 的当前位是否能够为 $1$,如果可以则必然要令这一位为 $1$。我们在这里只讨论 $u$ 有两个儿子的情形,一个儿子是类似的。若希望 $ans$ 的当前位为 $1$,我们必须将 $u$ 一个儿子子树内的所有数都划分进加法部分(这个操作在后文被称作删除)。同时,我们还需要加法部分对答案的贡献也 $\ge ans\mid 2^d$,其中 $2^d$ 即为当前位的权值。

具体来说,我们可以枚举要删除哪个儿子并判断是否可行,首先我们需要儿子子树内所有数对应的 $b_i$ 之和不超过剩余可用的代价,其次加法部分的数的最小值 $mn$ 需要满足 $mn+(x\mid(2^d-1))\ge ans\mid 2^d$,这里是在考虑加法部分在最优情形下对答案的贡献,在无需关心异或部分取值时,$x$ 的值可以任取,所以我们将还未确定的低位全部置为了 $1$。如果发现删除某一个儿子可以使得答案的当前位为 $1$,就对应的更新 $ans$ 与 $x$,朝另一个儿子递归即可。

若我们确定了 $ans$ 的当前位不可能为 $1$,首先我们仍考虑是否要删除一棵子树并令异或部分的当前位为 $1$,此时异或部分必然不会对最终的答案产生限制,只需要考虑加法部分,很显然,最优解即是将剩下的低位全部置为 $1$,所以我们直接用算出的数更新全局答案即可。现在只剩下不删除子树的情况,我们枚举 $x$ 的取值,此时当前位异或为 $1$ 的子树不会对答案产生限制,朝异或为 $0$ 的子树递归即可。

在任何情况下,我们都只会朝 $u$ 的每个儿子递归不超过一次,trie 上的每个结点至多被访问一次,所以总复杂度为 $O(nk)$。

Code

此为考场代码加上少量修改,仅供参考,变量命名与含义和题解中不完全一致。

namespace Main{
    const int N = 100005;
    const int S = 12000005;
    const __int128_t inf = __int128_t(1) << 120;
    int c, t, n, m, k, b[N], ch[S][2], tot;
    __int128_t a[N], mn[S], g_ans;
    ll sum[S];
    void insert(__int128_t x, int cost){
        int p = 0;
        for(int i = k - 1; i >= 0; i--){
            if(!ch[p][x >> i & 1]){
                ch[p][x >> i & 1] = ++tot;
                mn[tot] = inf;
                sum[tot] = 0;
            }
            p = ch[p][x >> i & 1];
            mn[p] = min(mn[p], x);
            sum[p] += cost;
        }
    }
    void solve(int p, int d, __int128_t mn_val, ll cost, __int128_t x, __int128_t ans){
        if(d == 0){
            g_ans = max(g_ans, min(mn_val + x, ans));
            return;
        }
        __int128_t one = 1; one <<= d - 1;
        if(!ch[p][0] || !ch[p][1]){
            if(ch[p][0]){
                solve(ch[p][0], d - 1, mn_val, cost, x | one, ans | one);
            }
            if(ch[p][1]){
                if(x + mn_val > ans) solve(ch[p][1], d - 1, mn_val, cost, x, ans | one);
                else{
                    g_ans = max(g_ans, x + mn_val + one - 1);
                    solve(ch[p][1], d - 1, mn_val, cost, x | one, ans);
                }
            }
            return;
        }
        bool del = false;
        if(cost + sum[ch[p][0]] <= m){
            if(min(mn_val, mn[ch[p][0]]) + x > ans){
                solve(ch[p][1], d - 1, min(mn_val, mn[ch[p][0]]), cost + sum[ch[p][0]], x, ans | one);
                del = true;
            }
            else{
                g_ans = max(g_ans, min(mn_val, mn[ch[p][0]]) + x + one - 1);
            }
        }
        if(cost + sum[ch[p][1]] <= m){
            if(min(mn_val, mn[ch[p][1]]) + x + one > ans){
                solve(ch[p][0], d - 1, min(mn_val, mn[ch[p][1]]), cost + sum[ch[p][1]], x | one, ans | one);
                del = true;
            }
            else{
                g_ans = max(g_ans, min(mn_val, mn[ch[p][1]]) + x + one + one - 1);
            }
        }
        if(!del){
            solve(ch[p][0], d - 1, mn_val, cost, x, ans);
            solve(ch[p][1], d - 1, mn_val, cost, x | one, ans);
        }
    }
    void Main(){
        input(c, t);
        while(t--){
            for(int i = 0; i <= tot; i++) ch[i][0] = ch[i][1] = 0;
            g_ans = 0, tot = 0;
            input(n, m, k);
            __int128_t mn_a = inf; ll sum_b = 0;
            for(int i = 1; i <= n; i++) input(a[i]), mn_a = min(mn_a, a[i]);
            for(int i = 1; i <= n; i++) input(b[i]), sum_b += b[i];
            if(sum_b <= m) g_ans = mn_a + (__int128_t(1) << k) - 1;
            for(int i = 1; i <= n; i++) insert(a[i], b[i]);
            solve(0, k, inf, 0, 0, 0);
            write(g_ans);
            puts("");
        }
        return;
    }
} // namespace Main
Last Modified: March 9, 2024
Archives Tip
QR Code for this page
Tipping QR Code