小C的利是 题解
一、题目:
二、思路:
这道题思路非常妙😄,我们先理清思路,然后再来说这种思路为什么妙。
先考虑如果 \(a\) 中没有 \(-1\) 的情况。
第一步,找到一个质数 \(P\),满足 \(P\equiv 1\pmod K\)。即 \(K|(P-1)\)。
第二步,找到模 \(P\) 的一个原根 \(g'\)。
第三步,令 \(g=(g')^{(P-1)/K}\)。注意,此时我们求出的 \(g\) 是模 \(P\) 意义下的 \(K\) 次单位根。这里的单位根可以类比复数中的单位根。
第四步,对于每个 \(i\in[0,K-1]\),令 \(x_i=g^i\),构造如下矩阵:
其中,\(b\) 是随机生成的一个矩阵。如果一个位置的 \(a\) 是 \(-1\),那么就把对应位置上的值修改为 \(0\)。
第五步,令 \(s=\sum\limits_{i=0}^{K-1}\det (A_i)\)。若 \(s\neq 0\),则输出 Yes
,否则输出 No
。
这样做为什么是正确的呢?
考虑一个排列 \(p\) 对 \(s\) 的贡献。设 \(S=\sum\limits_{i=1}^n a_{i,p_i}\)。则它对 \(s\) 的贡献就是
其中,前面的那一大坨是一个无关紧要的数,我们设为常数 \(C\)。
则上式为
这是一个等比数列。
我们发现,当 \(S\equiv 0\pmod K\) 时,上式在模 \(P\) 意义下等于 \(C\times K\)。
当 \(S\not\equiv0\pmod K\) 时,上式用等比数列求和公式,可得 \(C\times \dfrac{g^{KS}-1}{g^S-1}\)。由于 \(g^K\equiv1\pmod P\),所以上式等于 \(0\)。
所以,只要存在排列 \(p\),使得 \(S\equiv 0\pmod K\),\(s\) 在很大概率上就不会等于 \(0\)。反之,\(s\) 就一定等于 \(0\)。
现在看出来了吧,\(b\) 这个矩阵的作用就是使得我们检验正确的概率加大。
这道题妙就妙在,它运用了行列式可以遍历所有排列的特点,而且我们又可以在 \(O(n^3)\) 的时间内求出行列式的值。
三、代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
using namespace std;
#define FILEIN(s) freopen(s".in", "r", stdin);
#define FILEOUT(s) freopen(s".out", "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)
#define int long long
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 105;
int n, K, P, g, X, b[maxn][maxn], a[maxn][maxn];
int num[maxn][maxn];
inline bool check(int x) {
if (x == 1) return false;
if (x == 2) return true;
for (int i = 2; i * i <= x; ++ i)
if (x % i == 0) return false;
return true;
}
void find(void) {
for (int i = 1; ; ++ i)
if (check(i * K + 1)) {
P = i * K + 1; return;
}
}
inline int power(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
void find_root(void) {
g = 2;
for (; ; ++ g) {
bool flag = true;
for (int i = 2; i * i <= P - 1; ++ i) {
if ((P - 1) % i == 0) {
if (power(g, i, P) == 1) { flag = false; break; }
if (power(g, (P - 1) / i, P) == 1) { flag = false; break; }
}
}
if (flag) break;
}
g = power(g, (P - 1) / K, P);
}
void build(void) {
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
if (a[i][j] == -1) num[i][j] = 0;
else {
num[i][j] = b[i][j] * power(X, a[i][j], P) % P;
}
}
}
}
inline int Gauss(void) {
int res = 1;
for (int i = 1; i <= n; ++ i) {
for (int j = i + 1; j <= n; ++ j) {
while (num[i][i]) {
int r = num[j][i] / num[i][i];
for (int k = i; k <= n; ++ k) {
num[j][k] = (num[j][k] - r * num[i][k] % P + P) % P;
}
res = -res;
swap(num[i], num[j]);
}
swap(num[i], num[j]); res = -res;
}
}
for (int i = 1; i <= n; ++ i) {
res = res * num[i][i] % P;
}
(res += P) %= P;
return res;
}
signed main() {
FILEIN("luckymoney"); FILEOUT("luckymoney");
srand((unsigned)time(0));
n = read(); K = read();
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
a[i][j] = read();
b[i][j] = rand() + 1;
}
}
find();
find_root();
int res = 0;
for (int i = 0; i <= K - 1; ++ i) {
X = power(g, i, P);
build();
(res += Gauss()) %= P;
}
if (res) puts("Yes");
else puts("No");
return 0;
}