模拟赛题。
考虑操作的构成,先忽略 \(1\) 操作,只考虑任意两个数的异或,不难发现所有能构成的数即为线性基。
再考虑 \(1\) 操作,显然可以对开始的每个数率先进行 \(1\) 操作再构建线性基。
记 \(lim = \max (\log_2 a, \log_2 m)\),发现所有可能有效的数都不超过 \(2^{2lim}\)。
再考虑构建出线性基后如何求答案,从高位到低位考虑,设考虑到第 \(i\) 位,且前面的位都达到了 \(m\) 的上界,转移分类讨论该位置是否选择即可。
容易发现上面所有操作都能用 bitset 优化。
复杂度瓶颈在于构建线性基,朴素实现是 \(O(\frac{lim^3}{w})\) 的,很遗憾是跑不过的,注意这里会进行 \(lim^2\) 次异或,原因是单个数的 \(1\) 个数最大为 \(lim\),而进行 \(lim\) 次 \(1\) 操作每次需要重新插入。
对于重复插入同一个串多次使用 \(1\) 操作的情况,可以不用每次插入 \(a \times 2^k\),而是将 \(a\) 在线性基中异或完的结果插入,根据线性基的性质正确性得到保证,而 \(1\) 的个数总共为 \(lim\) 个,所以复杂度变为 \(O(\frac{lim^2}{w})\)。
考场忘了加取址符,曹操了。
code
const int mod = 998244353;
const int N = 8005;
#define bit bitset <N>
int n, lim;
void read_bit(bit &x) {
string s;
cin >> s;
reverse(all(s));
for (int i = 0; i < (int)s.length(); i++) x[i] = (s[i] - '0' ? 1 : 0);
}
int getlg(bit &x) {
if (!x.count()) return -1;
for (int i = N - 1; ;i--) if (x[i]) return i;
myassert(0);
}
bit m, a[10];
bit p[N], vis;
void ins(bit &x) {
for (int i = lim + 1; i >= 0; i--) {
if (!x[i]) continue;
if (!vis[i]) {p[i] = x, vis[i] = 1; break;}
x ^= p[i];
}
}
int cnt[N], pw[N];
void main01() {
ios::sync_with_stdio(0), cin.tie(0);
read(n);
read_bit(m);
for (int i = 0; i < n; i++) read_bit(a[i]);
if (!m.count()) {
cout << 1 << '\n';
return ;
}
lim = 0;
for (int i = 0; i < n; i++) ckmax(lim, getlg(a[i]));
lim += getlg(m);
for (int i = 0; i < n; i++) {
for (int c = getlg(a[i]); c <= lim + 1; c++, a[i] <<= 1) ins(a[i]);
}
int mxlen = getlg(m);
for (int i = 0; i <= mxlen; i++) cnt[i] = (i ? cnt[i - 1] : 0) + vis[i];
pw[0] = 1;
for (int i = 1; i <= mxlen + 1; i++) pw[i] = 1ll * pw[i - 1] * 2 % mod;
bit now; now.reset();
int ans = 0, op = 0;
for (int i = mxlen; i >= 0; i--) {
if (m[i]) {
if (now[i]) {
if (!vis[i]) continue;
ans += (i ? pw[cnt[i - 1]] : 1);
ans %= mod;
} else {
ans += (i ? pw[cnt[i - 1]] : 1);
ans %= mod;
if (!vis[i]) {op = 1; break;}
now ^= p[i];
}
} else {
if (now[i]) {
if (!vis[i]) {op = 1; break;}
now ^= p[i];
}
}
}
if (!op) (ans += 1) %= mod;
cout << ans << '\n';
}