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。

所以总方案数就是:

\[ans = 2^{(n - 1)(m - 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;
}

End

posted @ 2021-11-02 00:05  xixike  阅读(37)  评论(0编辑  收藏  举报