CF40E Number Table 题解
Description
输入格式
第 1 行两个整数 \(n\),\(m\),表示一个 \(n \times m\) 的矩阵。
第 2 行一个整数 \(k\),表示有 \(k\) 个点已经被填上数。
第 \(3\) ~ \(k + 2\) 行,每行 3 个整数 \(a\),\(b\),\(c\),表示矩阵\(M_{a,b} = c\)。
第 \(k + 3\) 行,1 个整数 \(p\),表示模数。
输出格式
一个整数,表示方案数。
Solution
我们首先考虑填一个空的矩阵有多少种方案数。
对于 1 ~ \(n - 1\) 行,我们先把最后一个数单独取出来一个数,剩下前 \(m - 1\) 个数随便填,最后一个数可以让这一行的乘积变成 -1。
同理,不管前 \(n - 1\) 行怎么填,最后一行总能让每一列的乘积变为 -1。
所以总方案数就是:
下面我们来考虑一下这道题。
首先可以判一波无解,先把结论放在这里:\(n\),\(m\) 奇偶性不同时,无解。
证明:
每一行,每一列乘积都为 -1,我们可以把这些 -1 乘在一起,即\((-1)^{n + m}\),再考虑一下这些数乘起来的本质是什么,是不是矩阵中每一个点的平方的乘积呢?是的。也就是说,乘积必为正数,就是 1。但当 \(n\),\(m\) 奇偶数性不同时,乘积是 -1,矛盾。
证毕.
我们继续来分析,观察到最后一句话:\(0 \leq k < max(n, m)\)
这有什么用呢?
我们假设 \(n > m\),上面的条件就变为了 \(k < n\),也就是说,不论你怎么填,必定有一行是空的(一个数都没有)。
我们发现,这样就有上面简化版题目的性质了,即,我们可以不考虑列了。
这时我们也可以套用上面的解法了,\(O(n)\) 枚举每一行,\(ans = ans \times 2^{m - cnt_i - 1}\),\(cnt_i\) 是第 \(i\) 行 1 的个数,减去 1 是因为要空出来一个数来使这一行的乘积为 -1。
注意:这里也要判一下无解,当有一行全部被填满且乘积为 1 时,无解(显然)。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m, k, mod, flag;
int x[N], y[N], z[N];
int line[N], mul[N], fac[N];
int main(){
scanf("%d%d%d", &n, &m, &k);
if((n + m) & 1){
puts("0");
return 0;
}
if(n < m) swap(n, m), flag = 1;
for(int i = 1; i <= n; ++i) mul[i] = 1;
for(int i = 1, x, y, z; i <= k; ++i){
scanf("%d%d%d", &x, &y, &z);
if(flag) swap(x, y);
line[x]++, mul[x] *= z;
}
scanf("%d", &mod);
fac[0] = 1;
for(int i = 1; i <= n; ++i)
fac[i] = fac[i - 1] * 2 % mod;
int ans = 1;
for(int i = 1; i < n; ++i)
if(!line[i]){
swap(line[i], line[n]), swap(mul[i], mul[n]);
break;
}
for(int i = 1; i < n && ans; ++i){
if(line[i] == m && mul[i] == 1) ans = 0;
if(line[i] < m) ans = ans * (long long)fac[m - line[i] - 1] % mod;
}
printf("%d\n", ans);
return 0;
}