0%

Nimber

有关nimber定义性质见 https://www.cnblogs.com/suwakow/p/12200462.html 。

ABC274EX

利用Nimber实现异或哈希

题目描述

给定字串 \(S=(S_1,\cdots,S_n)\) ,记 \(S_{l,r}\)\(S\)\(l,r\) 范围内的子串,记 \(f(S,T)=(S_1\oplus T_1,\cdots,S_n\oplus T_n)\)

每次询问给定 \(a,b,c,d,e,f\)\(f(S_{a,b},S_{c,d})=S_{e,f}\) 是否成立。

题解

判断两字串是否相等有哈希算法,我们用 Nim 和替代加法,用 Nim 积替代乘法,来得到新的哈希。

\[ Hash(S)=\sum_{i=0}^{n-1}S_{i+1}x^i=S_1\oplus (S_2\otimes x)\oplus \cdots\oplus (S_n\otimes x\otimes x\otimes \cdots \otimes x) \]

我们发现该哈希有性质 \(H(S)\oplus H(T)=H(S\oplus T)\) ,于是原问题解决。

PtzCamp Day5 Problem D-Two Missing Nimbers

或者是万能的Leetcode

题目描述

给定长度为 \(n\) 的数组 \(a_i\) (ull 范围),其中有2个数出现一次,其余均出现两次,问这两个数是啥,只允许扫一遍,且至多存128bit(2 ull)的数据。

题解

对于有限域 \(<\mathbb{Z}\bigcap[0,2^{2^{m}}),\oplus,\otimes>\) 上的加法操作为按位异或,可以很好地消除出现2次的数的影响,于是我们在此域上计算,以下加法均为 \(\oplus\) ,乘法均为 \(\otimes\) (nim product)。

记答案为 \(x,y\) ,我们求 \(\sum a_i=x+y\)\(\sum a_i^3=x^3+y^3=(x+y)(x^2+xy+y^2)\) ,故我们可以得到 \(e=x+y\)\(f=xy\) 的值,则 \(x,y\) 是该域上二次方程 \(x^2+ex+f=0\) 的两根。需要注意的是我们不能直接通过求 \(\sum a_i^2=x^2+y^2\) 获得 \(xy\) 的值,因为 \((x+y)^2=x^2+xy+yx+y^2=x^2+y^2\)

我们尝试对该式分解因式,注意不能直接分解为 \((x+\frac{e}{2})^2=f+\frac{e^4}{4}\) 。我们考虑构造出一个次数较高且根分布随机的多项式,通过求此式与二次式的gcd来分解二次式, \((x+r)^{(2^{64}-1)/3}-1\) 是一个较好的选择,其中 \(r\) 随机再加上nim product意义下 \(x^2\) 分布足够均匀,我们相信可以通过极少次随机得到二次式的一个因式,于是问题解决。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <bits/stdc++.h>
#define fi first
#define se second
#define Mp make_pair
#define pb push_back
#define SZ(a) (int(a.size()))

typedef long long ll;
typedef double db;
typedef std::pair<int, int> pii;
typedef std::vector<int> vi;
#define debug(...) fprintf(stderr, __VA_ARGS__)
std::mt19937_64 gen(std::chrono::system_clock::now().time_since_epoch().count());
ll get(ll l, ll r) { std::uniform_int_distribution<ll> dist(l, r); return dist(gen); }

// https://www.cnblogs.com/suwakow/p/12200462.html
typedef unsigned long long ull;
int rem[256][256];
ull nimProd(ull x, ull y, int p = 32) {
if (x <= 1 || y <= 1) return x * y;
if (p < 8 && rem[x][y]) return rem[x][y];
// a: a1 b: a0 c: b1 d: b0
ull a = x >> p, b = ((1ull << p) - 1) & x, c = y >> p, d = ((1ull << p) - 1) & y;
ull bd = nimProd(b, d, p >> 1), ac = nimProd(nimProd(a, c, p >> 1), 1ull << p >> 1, p >> 1), ans;
ans = ((nimProd(a ^ b, c ^ d, p >> 1) ^ bd) << p) ^ ac ^ bd;
if (p < 8) rem[x][y] = rem[y][x] = ans;
return ans;
}

template<class T>
T power(T a, ull b) {
T res = 1;
while(b) {
if(b & 1) res *= a;
a *= a, b >>= 1;
}
return res;
}
struct nimber {
ull z;
nimber(ull _z = 0) : z(_z) {}
ull val() { return z; }
friend bool operator==(nimber a, nimber b) { return a.z == b.z; }
friend nimber operator+(nimber a, nimber b) { return nimber(a.z ^ b.z); }
friend nimber operator-(nimber a, nimber b) { return nimber(a.z ^ b.z); }
friend nimber operator*(nimber a, nimber b) { return nimber(nimProd(a.z, b.z)); }
nimber &operator+=(nimber b) { return (*this) = (*this) + b; }
nimber &operator-=(nimber b) { return (*this) = (*this) + b; }
nimber &operator*=(nimber b) { return (*this) = (*this) * b; }
nimber inv() const { return power(*this, -2ull); }
nimber sqrt(nimber b) const { return power(b.z, 1ull << 63); }
friend nimber operator/(nimber a, nimber b) { return nimber(a.z * b.inv()); }
nimber &operator/=(nimber b) { return (*this) = (*this) / b; }
friend std::istream &operator>>(std::istream &is, nimber &a) {
ull v; is >> v; a = nimber(v); return is;
}
friend std::ostream &operator<<(std::ostream &os, nimber &a) {
return os << a.val();
}
};

struct poly {
std::vector<nimber> a;
poly(int size, int val = 0) : a(size, val) {}
poly(const std::vector<nimber> &ve) : a(ve) {}
int size() { return SZ(a); }
void resize(int n) { a.resize(n); }
friend poly operator*(poly a, poly b) {
poly c(SZ(a) + SZ(b));
for(int i = 0; i < SZ(a); i++)
for(int j = 0; j < SZ(b); j++)
c.a[i + j] += a.a[i] * b.a[j];
return c;
}
friend poly operator%(poly a, poly b) {
int id = SZ(b) - 1;
for(int i = SZ(a) - 1; i >= id; i--) {
auto d = a.a[i] / b.a[id];
for(int j = 0; j <= id; j++)
a.a[i - j] -= d * b.a[id - j];
}
while(a.a.back() == nimber(0)) a.a.pop_back();
return a;
}
friend poly operator-(poly a, poly b) {
assert(SZ(b) == 1);
a.a[0] -= b.a[0];
return a;
}
};

poly qpow(poly a, ull b, poly mod) {
poly res = poly({nimber(1)});
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod, b >>= 1;
}
return res;
}
poly gcd(poly a, poly b) { return SZ(b) ? gcd(b, a % b) : a; }

int q, n; nimber a, b;
signed main() {
std::ios::sync_with_stdio(false);
// std::cin >> q >> n;
// if(q == 2) std::cin >> a >> b;
std::cin >> n;
for(int i = 1; i <= n; i++) {
nimber x; std::cin >> x;
a += x, b += x * x * x;
}
if(q == 2) {
auto c = b / a;
auto d = c - a * a;
// x^2 + ax + d = 0
auto px = poly({d, a, 1});
nimber x, y;
while(1) {
auto r = gen();
auto pow = qpow(poly({r, nimber(1)}), -1ull / 3, px) - poly({nimber(1)});
// for(auto i : pow.a) std::cout << i << ' ';
// std::cout << std::endl;
auto g = gcd(pow, px);
if(SZ(g) == 2) {
x = g.a[0] / g.a[1];
break;
}
}
y = a - x;
a = x, b = y;
}
std::cout << a << ' ' << b << '\n';
std::cerr << (db)clock()/CLOCKS_PER_SEC << "s\n";
return 0;
}

这代码连Leetcode都过不去,PtzCamp输!

拓展阅读

2020年国家集训队论文集《浅谈 Nimber 和多项式算法》。

\(x^{2^{2^m}}\oplus x=x(x+1)(x+2)\cdots(x+2^{2^m}-1)\) 因为对于任意 \(x\) \(x^{2^{2^m}}\oplus x=0\) 恒成立,则由代数基本定理知上式成立。