[luoguP10218/省选联考 2024] 魔法手杖

题意

给定 \(a_1,a_2,\dots,a_n\) 以及 \(b_1,b_2,\dots,b_n\),满足 \(a_i \in [0,2^k-1]\) 以及 \(b_i\geq 0\),你需要给出 \(S \subseteq \{1,2,\dots,n\}\) 以及 \(x \in [0,2^k-1]\) 满足以下条件:

  • \(\sum \limits_{i\in S} b_i\leq m\)
  • 满足以上条件的前提下,最大化 \(val(S,x)=\min(\min \limits_{i \in S}(a_i+x),\min \limits_{i \in U \backslash S}(a_i \oplus x))\) 的值。

你只需要给出最大的 \(val(S,x)\) 的值即可。

sol

因为有异或操作,所以考虑 0/1Trie。由于二进制的特殊性,如果能够使高位更大的话,那么低位无论如何取都不会更优,符合贪心的思想。因此在 Trie 上遍历进行贪心。
遍历时,由于需要异或,如果该位答案取 \(1\),那么一定有一棵子树需要全部改用 \(+\) 操作(当且仅当需要更改操作的元素的 \(b\) 之和不超过 \(m\),且更改操作的元素的最小值加上可能的 \(x\) 的最大取值比当前答案的最小取值大时才可改用),否则只能取 \(0\)。因为不知道哪一棵子树需要更改操作,因此进行 dfs。
需要注意边界情况:

  1. 遍历完 \(k\) 位,此时遍历到的答案即为备选答案;
  2. Trie 树上不存在该节点,这意味着最终的答案是 \(a_i+x\),因此备选答案即为最小的更改操作元素加上可能的 \(x\) 最大取值。

INF 一定要开够

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
typedef __int128 I128;
typedef long long LL;

const int N = 100005, K = 125;
const I128 INF = 1e37;

int tr[N * K][2], idx;
I128 amin[N * K];
LL bsum[N * K];
int c, T;
int n, m, k;
int b[N];
I128 a[N];
I128 res = 0;

void read(__int128 &x){
	// read a __int128 variable
	char c; bool f = 0;
	while(((c = getchar()) < '0' || c > '9') && c != '-');
	if(c == '-'){f = 1; c = getchar();}
	x = c - '0';
	while((c = getchar()) >= '0' && c <= '9')x = x * 10 + c - '0';
	if(f) x = -x;
}

void write(__int128 x){
	// print a __int128 variable
	if(x < 0){putchar('-'); x = -x;}
	if(x > 9)write(x / 10);
	putchar(x % 10 + '0');
}

int create(){
    idx ++ ;
    tr[idx][0] = tr[idx][1] = 0;
    amin[idx] = INF, bsum[idx] = 0;
    return idx;
}

void insert(int x){
    I128 xc = a[x];
    int p = 1;
    for (int i = k - 1; i >= 0; i -- ){
        int c = 0;
        if (xc >= ((I128) 1 << i)) xc -= (I128) 1 << i, c = 1;
        if (!tr[p][c]) tr[p][c] = create();
        p = tr[p][c];
        // printf("@@@%d %d\n", c, p);
        amin[p] = min(amin[p], a[x]);
        bsum[p] += b[x];
    }
}

void debug(int pos, int k, I128 mina, LL sumb, I128 x, I128 ans){
    // printf("##%d %d ", pos, k);
    // write(mina);
    // printf(" %lld ", sumb);
    // write(x);
    // printf(" ");
    // write(ans);
    // puts("");
}

void dfs(int pos, int k, I128 mina, LL sumb, I128 x, I128 ans) {
    debug(pos, k, mina, sumb, x, ans);
    if (k <= -1) {
        res = max(res, ans);
        return ;
    }
    I128 cur = (I128) 1 << k, all = cur - 1;
    if (!pos) {
        res = max(res, mina + (x | cur | all));
        return ;
    }

    bool flag = false;
    int ls = tr[pos][0], rs = tr[pos][1];
    // printf("@!%d %d\n", ls, rs);
    // write(cur);
    // printf(" ");
    // write(all);
    // puts("");
    // printf("L!#!%d %d %lld ", pos, k, bsum[ls]);
    // write(min(amin[ls], mina) + (x | all));
    // putchar(' ');
    // write(ans | cur);
    // puts("");
    if (sumb + bsum[ls] <= m && min(amin[ls], mina) + (x | all) >= (ans | cur)) {
        // puts("LIF");
        flag = true;
        dfs(rs, k - 1, min(mina, amin[ls]), sumb + bsum[ls], x, ans | cur);
    }
    // printf("R!#!%d %d %lld ", pos, k, bsum[rs]);
    // write(min(amin[rs], mina) + (x | cur | all));
    // putchar(' ');
    // write(ans | cur);
    // puts("");
    if (sumb + bsum[rs] <= m && min(amin[rs], mina) + (x | cur | all) >= (ans | cur)) {
        // puts("RIF");
        flag = true;
        dfs(ls, k - 1, min(mina, amin[rs]), sumb + bsum[rs], x | cur, ans | cur);
    }
    if (flag) return ;
    dfs(ls, k - 1, mina, sumb, x, ans);
    dfs(rs, k - 1, mina, sumb, x | cur, ans);
}

int main(){
    // freopen("xor.in", "r", stdin);
    // freopen("ans.out", "w", stdout);
    scanf("%d%d", &c, &T);
    while (T -- ){
        idx = 0;
        create();
        amin[0] = INF;

        scanf("%d%d%d", &n, &m, &k);
        I128 mina = INF;
        LL sumb = 0;
        for (int i = 1; i <= n; i ++ ) read(a[i]), mina = min(mina, a[i]);
        for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]), sumb += b[i];

        if (sumb <= m) {
            write(mina + ((I128) 1 << k) - 1);
            puts("");
            continue;
        }
        
        for (int i = 1; i <= n; i ++ ) insert(i);

        // for (int i = 1; i <= idx; i ++ ) printf("%lld\n", bsum[i]);

        res = 0;
        dfs(1, k - 1, INF, 0ll, (I128) 0, (I128) 0);

        write(res);
        puts("");
    }
}

蒟蒻犯的若至错误

  • Trie 写挂了
  • Trie 写挂了 \(\times 2\)
  • INF 开小了
posted @ 2024-12-29 11:02  是一只小蒟蒻呀  阅读(3)  评论(0编辑  收藏  举报