首先考虑对于每个 \(i\),\(b_i\) 可能的取值范围是什么,不难发现 \(b_i\in\{x|x=\operatorname{xor}\limits_{y\in S}y,S\subseteq\{1,2,3,\cdots,i\},i\in S\}\),换句话,\(b_i\) 可能的取值范围为 \(\{1,2,3,\cdots,i-1\}\) 某个自己的异或和异或上 \(a_i\)。
由于涉及到子集的异或和,我们很自然地想到线性基,更具体地,我们将 \(a_1,a_2,\cdots,a_n\) 依次插入线性基,那么如果插入一个 \(a_i\) 时,线性基维数增大,那么对应的 \(b_i\) 的取值范围就是线性基中新增的这部分元素,否则 \(b_i\) 的取值范围就是此时此刻线性基中的元素。
注意到一个性质:如果我们称“插入 \(a_i\) 时,线性基大小发生改变”的 \(a_i\) 为关键点,那么关键点最多有 \(\log V\) 个,我们还发现,对于非关键点,其在 LIS 中的部分一定是一段后缀,因为对于非关键点而言,前面的部分的取值范围一定严格包含于前面的部分,我们肯定会选择尽量将这些非关键点上的值靠后放,这样答案一定不会变劣。
这样一来我们就可以只用 DP 关键点的位置即可,即,设关键点的位置为 \(p_1,p_2,\cdots,p_m\),那么我们设 \(dp_{i,j}\) 表示我们钦定了 \(p_i\sim n\) 的取值,目前 \(p_i,p_{i+1},\cdots,p_m\) 中有 \(j\) 个位置在 LIS 上,LIS 开头元素的最大值。转移相当于先加入一段非关键点,再加入一个关键点,非关键点的部分就找出 \(dp_{i,j}\) 在 \([1,p_{i}-1]\) 处前缀线性基中的排名,然后往前数 \(len\) 个再转化成数值,其中 \(len\) 表示非关键点个数,如果数到头了就直接更新答案并令 \(dp_{i,j}=0\)。关键点的部分相当于这样一个问题:给定一个线性基 \(\mathcal B\) 和两个数 \(X,Y\),求 \(X\oplus x\) 的最大值,满足 \(X\oplus x<Y\),且 \(x\) 可以被 \(\mathcal B\) 表示出来。这是一个经典问题,先找到 \(\mathcal B\) 的最小基 \(c_1,c_2,\cdots,c_k(c_i<c_{i+1})\),然后按下标递增的顺序考虑所有 \(c_i\),如果 \(X\oplus c_i<X\) 就令 \(X\) 异或上 \(c_i\)。再按下标递增的顺序考虑所有 \(c_i\),如果 \(X\oplus c_i<Y\) 就令 \(X\) 异或上 \(c_i\)。
时间复杂度 \(n\log V+\log^3V\),瓶颈在于一开始将所有 \(a_i\) 插入线性基求关键点的位置。
using namespace fastio;
const int MAXN = 1e6;
const int LOG_V = 60;
int n; ll a[MAXN + 5], bs[LOG_V + 2][LOG_V + 2], b[LOG_V + 2];
int tot = 0, cc[LOG_V + 2], cnt[LOG_V + 2]; ll val[LOG_V + 2], dp[LOG_V + 2];
bool ins(ll v) {
for (int i = LOG_V; ~i; i--) if (v >> i & 1) {
if (!b[i]) {
b[i] = v;
for (int j = i - 1; ~j; j--) if (b[i] >> j & 1) b[i] ^= b[j];
for (int j = i + 1; j <= LOG_V; j++) if (b[j] >> i & 1) b[j] ^= b[i];
return 1;
} else v ^= b[i];
}
return 0;
}
ll getrnk(int sz, ll *B, ll v) {
ll cur = 0, rk = 0;
for (int i = sz - 1; ~i; i--) if ((cur ^ B[i]) < v)
cur ^= B[i], rk += 1ll << i;
return rk;
}
ll getkth(int sz, ll *B, ll k) {
ll cur = 0;
for (int i = sz - 1; ~i; i--) if (k >> i & 1) cur ^= B[i];
return cur;
}
ll getnxt(int sz, ll *B, ll cur, ll nd) {
for (int i = sz - 1; ~i; i--) if ((cur ^ B[i]) < cur) cur ^= B[i];
if (cur >= nd) return 0;
for (int i = sz - 1; ~i; i--) if ((cur ^ B[i]) < nd) cur ^= B[i];
return cur;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i <= n; i++) {
if (ins(a[i])) {
tot++;
for (int j = 0; j <= LOG_V; j++) if (b[j])
bs[tot][cc[tot]++] = b[j];
val[tot] = a[i];
} else cnt[tot]++;
}
// printf("%d\n", tot);
dp[0] = LLONG_MAX; int ss = 0, res = 0;
for (int i = tot; i; i--) {
if (cnt[i]) {
for (int j = 0; j <= LOG_V; j++) if (dp[j]) {
ll rk = getrnk(cc[i], bs[i], dp[j]);
// printf("! %d %d %lld\n", i, j, rk);
if (rk < cnt[i]) {
chkmax(res, rk + j + ss + 1);
dp[j] = 0;
} else {
dp[j] = getkth(cc[i], bs[i], rk - cnt[i] + 1);
// printf("!! %lld\n", dp[j]);
}
}
}
for (int j = LOG_V; ~j; j--) if (dp[j]) {
chkmax(dp[j + 1], getnxt(cc[i - 1], bs[i - 1], val[i], dp[j]));
}
// for (int j = 0; j <= tot; j++) printf("%lld%c", dp[j], " \n"[j == tot]);
ss += cnt[i];
}
for (int i = 0; i <= tot; i++) if (dp[i]) chkmax(res, ss + i);
printf("%d\n", res);
return 0;
}