#include #include #define MAX_DIM 100 // p is always assumed to be positive and the representatives of all // field elements are assumed to belong to [0, p-1] in all functions. // Computes xy mod p. int mod_mul(int x, int y, int p) { return (x * y) % p; } // Computes (x+y) mod p. int mod_add(int x, int y, int p) { return (x + y) % p; } // Computes (x+y) mod p. int mod_sub(int x, int y, int p) { return (x + p - y) % p; } // Computes x^e mod p (assumes e is in [0,p-1]). // This is the left-to-right version of the square-and-multiply // modular exponentiation algorithm. This is slightly different from // the square-and-multiply modular exponentiation algorithm presented // in class. int mod_exp(int x, int e, int p) { int res = 1; for (int mask = (1 << 14); mask; mask >>= 1) { res = mod_mul(res, res, p); if (mask & e) { res = mod_mul(res, x, p); } } return res; } // Computes x^(-1) mod p using Fermat's little theorem. This version // is fairly slow, since it corresponds to modular exponentiation. int mod_inv(int x, int p) { return mod_exp(x, p - 2, p); } // Replaces row by s * row mod p, i.e., scalar multiplication. void mod_mulrow(int row[], int s, int p, int dim) { for (int i = 0; i < dim; i++) { row[i] = mod_mul(row[i], s, p); } } // Replaces row1 by row1 + row2, i.e., vector addition. void mod_addrow(int row1[], int row2[], int p, int dim) { for (int i = 0; i < dim; i++) { row1[i] = mod_add(row1[i], row2[i], p); } } // Replaces row1 by row1 - s*row2. void mod_mulsubrow(int row1[], int row2[], int s, int p, int dim) { int tmp[MAX_DIM]; int negs = mod_sub(0, s, p); memcpy(tmp, row2, dim * sizeof(int)); mod_mulrow(tmp, negs, p, dim); mod_addrow(row1, tmp, p, dim); } // Dump current state of matrix and vector. Used for debugging. void dump(int m[MAX_DIM][MAX_DIM], int b[MAX_DIM], int dim) { for (int i = 0; i < dim; i++) { for (int j = 0; j < dim; j++) { printf("%2d ", m[i][j]); } printf("| %2d\n", b[i]); } } // Naive implementation of Gaussian elimination under the assumption // that the input matrix is invertible modulo p, i.e., m is an // invertible (dim x dim)-matrix over GF(p). More concretely, this // function replaces b by m^(-1) * b, where * denotes matrix // multiplication. int gaussian_solve(int m[MAX_DIM][MAX_DIM], int b[], int p, int dim) { for (int i = 0; i < dim; i++) { // Make sure diagonal is non-zero and update b correspondingly. if (m[i][i] == 0) { int j = i + 1; while (j < dim && m[j][i] == 0) { j++; } mod_addrow(m[i], m[j], p, dim); b[i] = mod_add(b[i], b[j], p); } // Make diagonal equal to one and update b correspondingly. int inv = mod_inv(m[i][i], p); mod_mulrow(m[i], inv, p, dim); b[i] = mod_mul(b[i], inv, p); // Make everything else in m[][] zero and update b correspondingly. for (int l = 0; l < dim; l++) { if (l != i && m[l][i] != 0) { b[l] = mod_sub(b[l], mod_mul(b[i], m[l][i], p), p); mod_mulsubrow(m[l], m[i], m[l][i], p, dim); } } } } // Initializes m to the (dim x dim) Vandermonde matrix. void vandermonde(int m[MAX_DIM][MAX_DIM], int p, int dim) { for (int i = 0; i < dim; i++) { for (int j = 0; j < dim; j++) { m[i][j] = mod_exp(i+1, j, p); } } } // Translate ascii string into integers. int ascii2int(char *cptr, int *iptr) { int *tmp = iptr; while (*cptr != '\0') { if (*cptr == '*') { *iptr = 0; } else { *iptr = *cptr - 'a' + 1; } cptr++, iptr++; } return iptr - tmp; } int main(void) { int cases; char s[MAX_DIM]; int b[MAX_DIM]; int m[MAX_DIM][MAX_DIM]; scanf("%d", &cases); int p; for (int i = 0; i < cases; i++) { scanf("%d %s", &p, s); int dim = ascii2int(s, b); vandermonde(m, p, dim); gaussian_solve(m, b, p, dim); printf("%d", b[0]); for (int i = 1; i < dim; i++) { printf(" %d", b[i]); } printf("\n"); } }