P10218 [省选联考 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)\) 的值即可。
\(n \le 5 \times 10^5, k \le 120,a_{i} < 2^{k},b_{i} \le 10^{9}\)。
分析:
先特判掉 \(ans\) 可以大于 \(k\) 位的情况,即满足 \(\sum b \le m\) 且 \(\min a_{i} + 2^{k}-1\) 的位数大于 \(k\)。
考虑先确定 \(ans\) 的第 \(k\) 位。即判断 \(ans\) 的第 \(k\) 位是否能为 \(1\)。先建一棵 0/1 Trie。
-
如果 \(x\) 的第 \(k\) 位为 \(0\):因为左子树(如果存在)的 \(a_{i}\) 的第 \(k\) 位为 \(0\),那么左子树的 \(a_{i}\) 必须全在加法集合里。要满足 \(\sum_{i \in left} b_{i} \le m\)。把 \(x\) 的第 \(k-1\) 位到第 \(1\) 位全都看成 \(1\)(极端情况)。判断 \(\min_{i \in right} a_{i}+x\) 的第 \(k\) 为是否为 \(0\)。至于右子树全划分在异或集合里即可。
-
如果 \(x\) 的第 \(k\) 位为 \(1\):处理方法与上面对称。
尝试递归处理计算 \(ans\),还是刚刚的思路(具体处理要略微修改一下 ):
-
如果 \(x\) 的第 \(k\) 位为 \(0\):
- 如果 \(ans\) 能取到 \(1\):左子树全在加法集合里,记个加法集合的最小值,更新 \(m\),递归进入右子树。
- 否则,右子树全在异或集合里,递归进入左子树。
-
如果 \(x\) 的第 \(k\) 位为 \(1\):
- 如果 \(ans\) 能取到 \(1\):右子树全在加法集合里,记个加法集合的最小值,更新 \(m\),递归进入左子树。
- 否则,左子树全在异或集合里,递归进入右子树。
时间复杂度 \(O(size(trie))\),即 \(O(k \sum n)\)。
代码:
#include<bits/stdc++.h>
#define int long long
#define ll __int128
#define N 12000006
#define M 100005
#define il inline
#define ls tree[u][0]
#define rs tree[u][1]
using namespace std;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++);
char buf[1 << 23], *p1 = buf, *p2 = buf, ubuf[1 << 23], *u = ubuf;
il ll read() {
ll p = 0, flag = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') flag = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
p = p * (ll)10 + c - '0';
c = getchar();
}
return p * flag;
}
void write(ll x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar('0' + x % 10);
}
int c, T, cnt = 1;
int n, m, k;
ll Ans;
ll a[M], Mina[N];
int b[M], sumb[N], tree[N][2];
il ll Min(ll x, ll y) {
return x < y ? x : y;
}
il ll Max(ll x, ll y) {
return x > y ? x : y;
}
il void Insert(int u, ll a, int b, int k) {
if(k == -1) {
Mina[u] = Min(Mina[u], a);
sumb[u] += b;
return;
}
int opt = 0;
if(a & ((ll)1 << k)) opt = 1;
if(!tree[u][opt]) tree[u][opt] = ++cnt;
Insert(tree[u][opt], a, b, k - 1);
Mina[u] = Min(Mina[ls], Mina[rs]);
sumb[u] = sumb[ls] + sumb[rs];
}
il void dfs(int u, int m, ll Minn, ll ans, ll x, int k) {
if(k == -1) {
Ans = Max(Ans, ans);
return;
}
ll A = ((ll)1 << (k + 1)) - 1, B = ((ll)1 << k);
if(u == 0) {
Ans = Max(Ans, Minn + (x | A));
return;
}
bool flag = 0;
if(m >= sumb[ls] && Min(Mina[ls], Minn) + (x | (B - 1)) >= (ans | B))
dfs(rs, m - sumb[ls], Min(Mina[ls], Minn), ans | B, x, k - 1), flag = 1;
if(m >= sumb[rs] && Min(Mina[rs], Minn) + (x | A) >= (ans | B))
dfs(ls, m - sumb[rs], Min(Mina[rs], Minn), ans | B, x | B, k - 1), flag = 1;
if(!flag) dfs(ls, m, Minn, ans, x, k - 1), dfs(rs, m, Minn, ans, x | B, k - 1);
}
void Sol() {
n = read(), m = read(), k = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= n; i++) b[i] = read();
for(int i = 1; i <= n; i++) Insert(1, a[i], b[i], k - 1);
ll res = Mina[1] + ((ll)1 << k) - 1;
if(sumb[1] <= m && (res & ((ll)1 << k))) {
write(res); puts("");
for(int i = 0; i <= cnt; i++) Mina[i] = ((ll)1 << (ll)125), sumb[i] = tree[i][0] = tree[i][1] = 0;
cnt = 1;
return;
}
dfs(1, m, (ll)1 << (125), 0, 0, k - 1);
write(Ans); puts("");
Ans = 0;
for(int i = 0; i <= cnt; i++) Mina[i] = ((ll)1 << (ll)125), sumb[i] = tree[i][0] = tree[i][1] = 0;
cnt = 1;
}
signed main() {
//freopen("25.in", "r", stdin);
//freopen("xor.out", "w", stdout);
for(int i = 0; i <= 12000000; i++) Mina[i] = ((ll)1 << (ll)125);
c = read(), T = read();
while(T--) Sol();
return 0;
}
/*
1 1
2 1 28
42882440 58170422
1 2
*/