Loading

[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\) 这种操作,考虑其相当于左移。而左移对异或是有分配率的,即:

\[(A\oplus B)\ll 1 = (A\ll 1) \oplus (B\ll 1) \]

所以,只需要在线性基中将每个数左移任意多位的数加进去即可。那么,到底需要加到左移多少位呢?可能有人觉得左移到二进制下长度为 \(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$。则

\[T=U\oplus (A_i\ll 1) \]

又因为 \(A_i=B \oplus V\),其中 \(V\) 是线性基中能够组出的一个异或和。则将其带入上式中,

\[\begin{align*} T&=U\oplus (A_i\ll 1)\\ &=U\oplus ((B\oplus V)\ll 1)\\ &=U\oplus (B\ll 1) \oplus (V\ll 1) \end{align*} \]

\(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;
}
posted @ 2025-04-15 10:53  Pigsyy  阅读(20)  评论(0)    收藏  举报