[luogu 3923]. 大学数学题
题意
构造一个\(n\)元有限域,若不存在输出-1,否则域中元素分别用\(0, 1, 2, \ldots, n - 1\)表示,输出加法表和乘法表。
\(n \leq 343\)。
题解
你我真的会域扩张吗?答:不会
首先,根据一些基础知识,有解当且仅当\(n = p ^ d\),其中\(p \in \mathbb{P}\)。
其次,根据域扩张的知识,我们要构造的\(\text{GF}(p ^ d)\)一定是由素域\(\text{GF}(p)\)有限扩张而来的。
考察在\(\text{GF}(p)\)下的代数扩张,即假设存在一个\(\text{GF}(p)[[x]]\)下的不可约多项式\(f(x)\),设\(\zeta\)是这个多项式的一个根,那么\(\text{GF}(p ^ d)\)中的所有元素可以唯一表示成一个多项式
(实际上是基\(1, \zeta, {\zeta} ^ 2, \ldots, {\zeta} ^ {d - 1}\)的线性组合)
假设我们最后要输出的是这些多项式的加法表和乘法表,那么问题就转化为了找到一个合适的加法运算和乘法运算。
加法就是在域\(\text{GF}(p)[[x]]\)下的多项式加法,乘法就是在域\(\text{GF}(p)[[x]]\)下的多项式乘法。
但是多项式乘法会导致使用的基的大小变大,我们需要把基的大小缩到\(d\)。
考虑可以应用线性组合\(f(\zeta) = 0\)。
对于一个多项式,加上或减去\(k\)倍的\(f(\zeta)\)(\(k \in \text{GF}(p)[[x]]\)),代入\(\zeta\)求值不变。
那么对于一个多项式,如果次数超过了\(d - 1\),可以将它对\(f(\zeta)\)取模,得到的结果是一个次数小于等于\(d - 1\)的多项式(即基的大小缩到了\(d\)),并且代入\(\zeta\)后值和原多项式值相等。
现在考虑的是我们输出的是一个数表而不是多项式表,考虑到要把多项式\(F\)映射到数\(f\),并且要求这个映射可逆。
如果有办法可以把\(\zeta\)搞出来,那么当然可以直接将\(F\)映射到\(F(\zeta)\)上,这是个同构映射,显然可逆。
但是求\(\zeta\)是不现实的,我们退而求其次,找一个要求不太高的可逆映射即可,比如把多项式\(F\)的系数看做数\(f\)在\(p\)进制下每一位的值,可以验证这是可行的。
复杂度\(\mathcal O(n ^ 2 {\log} ^ 2 n)\)。
#include <bits/stdc++.h>
using namespace std;
typedef vector <int> poly;
const int N = 360, M = 10;
const int R[7][8] = {
{1, 7, 11, 19, 37, 67, 131, 283}, // 2, 4, 8, 16, 32, 64, 128, 256
{1, 10, 34, 86, 250, -1, -1, -1}, // 3, 9, 27, 81, 243
{1, 27, 131, -1, -1, -1, -1, -1}, // 5, 25, 125
{1, 50, 345, -1, -1, -1, -1, -1}, // 7, 49, 343
{1, 122, -1, -1, -1, -1, -1, -1}, // 11, 121
{1, 171, -1, -1, -1, -1, -1, -1}, // 13, 169
{1, 292, -1, -1, -1, -1, -1, -1}, // 17, 289
};
int n, id[N], vis[N]; vector <int> pr;
void sieve () {
for (int i = 2; i < N; ++i) {
if (!vis[i]) {
id[i] = pr.size(), pr.push_back(i);
for (int j = i; j < N; j += i) {
vis[j] = i;
}
}
}
}
namespace GF {
int m, p, d, pl[M];
struct elem {
int x;
elem (int _x = 0) : x(_x) {}
elem compress (const poly &r) {
x = 0;
for (int i = d - 1; ~i; --i) {
x = x * p + r[i];
}
return *this;
}
poly expand (elem e) {
poly r(d);
for (int i = 0; i < d; ++i) {
r[i] = e.x % p, e.x /= p;
}
return r;
}
elem operator + (const elem &o) {
poly F = expand(*this), G = expand(o);
for (int i = 0; i < d; ++i) {
F[i] = (F[i] + G[i]) % p;
}
return compress(F);
}
elem operator * (const elem &o) {
poly F = expand(*this), G = expand(o), H(d + d, 0);
for (int i = 0; i < d; ++i) {
for (int j = 0; j < d; ++j) {
H[i + j] = (H[i + j] + F[i] * G[j]) % p;
}
}
for (int i = (d - 1) << 1; i > d - 1; --i) {
H[i] = -H[i] % p, H[i] += H[i] >> 31 & p;
for (int j = 1; j <= d; ++j) {
H[i - j] = (H[i - j] + H[i] * pl[d - j]) % p;
}
}
return compress(H);
}
} ;
void get_poly () {
for (int i = 0, x = R[id[p]][d - 1]; i <= d; ++i) {
pl[i] = x % p, x /= p;
}
}
} using namespace GF;
int main () {
sieve();
cin >> n, m = n, p = vis[n];
for (int x = n; x != 1; x /= p, ++d) {
if (x % p != 0) {
n = 1;
break;
}
}
if (n == 1) {
puts("-1");
} else {
puts("0");
get_poly();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
printf("%d%c", ((elem)i + (elem)j).x, j < n - 1 ? ' ' : '\n');
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
printf("%d%c", ((elem)i * (elem)j).x, j < n - 1 ? ' ' : '\n');
}
}
}
return 0;
}
/*
4 : x ^ 2 + x + 1 (111)2 = 7
8 : x ^ 3 + x + 1 (1011)2 = 11
9 : x ^ 2 + 1 (101)3 = 10
25 : x ^ 2 + 2 (102)5 = 27
27 : x ^ 3 + 2x + 1 (1021)3 = 34
81 : x ^ 4 + x + 2 (10012)3 = 86
121: x ^ 2 + 1 (101)11 = 122
125: x ^ 3 + x + 1 (1011)5 = 131
169: x ^ 2 + 2 (102)13 = 171
128: x ^ 7 + x + 1 (10000011)2 = 131
243: x ^ 5 + 2x + 1 (1000021)3 = 250
256: x ^ 8 + x ^ 4 + x ^ 3 + x + 1 (100011011)2 = 283
343: x ^ 3 + 2 (1002)7 = 345
*/