[ARC084D] XorShift
题意
给定 \(N\) 个整数 \(A_i\),接下来可以执行任意次操作:
- 选择整数 \(i\),将 \(2A_i\) 加入序列 \(A\)。
- 选择整数 \(i,j\),将 \(A_i\oplus A_j\) 加入序列 \(A\)。
试求无限长的序列 \(A\)中,小于等于 \(X\) 的数有多少个?
数据范围:\(1\le N\le 6\),\(1\le X,A_i< 2^{4000}\)。
思路
如果只有第 \(2\) 个操作,那么直接异或线性基即可。但是,现在存在 \(2A_i\) 这种操作,考虑其相当于左移。而左移对异或是有分配率的,即:
所以,只需要在线性基中将每个数左移任意多位的数加进去即可。那么,到底需要加到左移多少位呢?可能有人觉得左移到二进制下长度为 \(4000\) 就够了,因为 \(X\) 的限制也是 \(4000\)。然而,这是错的,因为有可能有些二进制串需要左移很多,从而超出去位异或没了,只剩下后 \(4000\) 位,而这样一些串之间能错位异或,就可能异或出更多的数。所以,只需要左移到二进制下长度为 \(8000\) 就够了,因为这时候任意两个串错位异或的情况均考虑到了。
分析复杂度,插入一个串的复杂度为 \(\frac{|S|^2}{w}\),而我们需要插入 \(O(n|S|)\) 个串,所以总复杂度为 \(O(\frac{n|S|^3}{w})\),无法通过。
这时候,瓶颈在于线性基,插入的字符串数肯定是无法再优化了。所以考虑优化在线性基中插入的过程,考虑进行均摊?在线性基中插入完 \(A_i\) 之后,记剩下的数(插到线性基中的数)为 \(B\),那么我们有必要重新将 \(A_i\ll 1\) 重新插一遍吗?没必要,可以直接将 \(B\ll 1\) 插入。
证明
思考加入 $A_i\ll 1$ 的本质含义,是为了能组出更多的数。通过线性基中本身存在的异或和 $U$ 和 $A_i\ll 1$,组成更多的数记作 $T$。则又因为 \(A_i=B \oplus V\),其中 \(V\) 是线性基中能够组出的一个异或和。则将其带入上式中,
而 \(V\ll 1\) 和 \(U\) 均可被线性基表示,所以只需要再在线性基中插入 \(B\ll 1\) 即可。
这样,每次就不需要重新枚举一遍 \(8000\) 个位置,而是总共枚举 \(O(8000)\) 个位置(可能有 \(2\) 倍左右的常数)。综上,时间复杂度降至 \(O(\frac{n|S|^2}{w})\)。
接下来,只需要计算线性基能够表示出的所有数中,小于等于 \(X\) 的数有多少个。直接数位 DP 即可,数位 DP 的本质在计算是高位固定,低位任意的方案数。所以,即使线性基不是最简形式,也是可以做的。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using u64 = unsigned long long;
struct BigInt {
u64 info[128];
BigInt() { memset(info, 0, sizeof info); }
BigInt(string s) {
memset(info, 0, sizeof info);
for (int i = 0; i < s.size(); i ++) {
u64 &v = info[(s.size() - i - 1) / 64];
v = v << 1 | (s[i] & 1);
}
}
BigInt operator^ (BigInt T) {
BigInt res;
for (int i = 0; i < 128; i += 4) {
res.info[i] = info[i] ^ T.info[i];
if (i + 1 < 128) res.info[i + 1] = info[i + 1] ^ T.info[i + 1];
if (i + 2 < 128) res.info[i + 2] = info[i + 2] ^ T.info[i + 2];
if (i + 3 < 128) res.info[i + 3] = info[i + 3] ^ T.info[i + 3];
}
return res;
}
BigInt operator^= (BigInt T) { return (*this) = (*this) ^ T; }
bool operator> (BigInt T) {
for (int i = 127; i >= 0; i --)
if (info[i] > T.info[i]) return 1;
else if (info[i] < T.info[i]) return 0;
return 0;
}
void set(int x) {
info[x / 64] |= (1ull << (x % 64));
}
bool operator[] (int x) {
return info[x / 64] >> (x % 64) & 1;
}
void print() {
string s;
u64 u = info[0];
while (u) s += char((u & 1) ^ 48), u /= 2;
reverse(s.begin(), s.end());
cout << s << endl;
}
};
struct Hamel {
BigInt base[8000];
BigInt vis;
void insert(string s) {
BigInt temp(s);
for (int i = 7999; i >= 0 && i < 8000; i --)
if (temp[i]) {
if (vis[i]) temp ^= base[i];
else {
base[i] = temp, vis.set(i);
bool jw = 0, t;
for (int j = 0; j <= 127; j ++ ) {
t = temp.info[j] >> 63 & 1;
temp.info[j] = temp.info[j] << 1 | jw;
jw = t;
}
i += 2;
}
}
}
}ds;
int main() {
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
int N;
string SX;
cin >> N >> SX;
BigInt X(SX);
for (int i = 1; i <= N; i ++) {
string T;
cin >> T;
ds.insert(T);
}
const int mod = 998244353;
std::vector<int> sum(8000), pw(8001, 1);
for (int i = 1; i <= 8000; i ++)
pw[i] = pw[i - 1] * 2 % mod;
for (int i = 0; i < 8000; i ++)
sum[i] = (!i ? 0 : sum[i - 1]) + ds.vis[i];
BigInt temp;
int res = 0, ok = 1;
for (int i = 7999; i >= 0; i --) {
if (!ds.vis[i]) {
if (temp[i] && !X[i]) {
ok = 0;
break;
}
if (!temp[i] && X[i]) {
ok = 0;
if (!i) res ++;
else (res += pw[sum[i - 1]]) %= mod;
break;
}
} else {
if (temp[i] && !X[i]) temp ^= ds.base[i];
if (temp[i] && X[i])
if (!i) res ++;
else (res += pw[sum[i - 1]]) %= mod;
if (!temp[i] && X[i]) {
if (!i) res ++;
else (res += pw[sum[i - 1]]) %= mod;
temp ^= ds.base[i];
}
}
}
res += ok;
cout << res % mod << endl;
return 0;
}

浙公网安备 33010602011771号