[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。
需要注意边界情况:
- 遍历完 \(k\) 位,此时遍历到的答案即为备选答案;
- 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 开小了