矩阵颜色

Problem

一个 \(n\times m\) 的矩阵,第 \(i\) 行第 \(j\) 列元素有一个颜色 \(c_{i,j}\),求所有子矩阵的颜色种类数的平均值。

\(n, m \le 100, c_{i, j} \le n \times m\)

Input

第一行两个正整数 \(n,m\)
接下来 \(n\) 行,每行 \(m\) 个正整数,表示元素的颜色。

Output

输出子矩阵的平均颜色数,保留 \(9\) 位小数。

Sample

Input 1

2 3
1 2 1
2 1 2

Output 1

1.666666667

Solution

容易发现,颜色之间互不影响,可以分开考虑每种颜色对答案的贡献。

如下图所示,涂色的格子表示同一颜色,我们将其按照遍历的顺序编号。

为了避免重复计算,我们规定在统计包含编号为 \(i\) 的格子的矩阵数量时,矩阵不能够包含编号为 \(j(j < i)\) 的格子。比如当前我们在计算 \(8\) 号格的贡献,矩阵就不能包含编号在 \([1,7]\) 的格子。这些格子相当于障碍,我们将这些障碍标记出来。

这些障碍规定了矩阵的左右区间范围,且满足单调性,第 \(i\) 行的左右范围一定不大于第 \(i+1\) 行的左右范围。如下图,将左右范围补全。

由于矩阵要包含目标格子,坐标记为 \((x,y)\),那么矩阵的上界一定在 \([1,x]\) 内。我们从第 \(x\) 行扫描到第 \(1\) 行,一边更新左右范围一边计算。

如下图,计算以第 \(5\) 行为上界时的矩阵范围(阴影部分)。

当扫到第 \(3\) 行时,范围缩小。

当上界为第 \(2\) 行时,范围再次缩小。

当上界为第 \(1\) 行时,发现上面有障碍阻拦,范围缩小至 \(0\)

那么确定了左右范围,如何统计贡献呢?

先看看一维的情况。

如图,我们要选一个包含 \(x\)点 的区间,区间范围在 \((l, r)\) 之间(\(l,r\) 两点是障碍,不能取到)。左端点范围在 \((l, x]\),有 \(x - l\) 种取值;右端点范围在 \([x, r)\),有 \(r - x\) 种取值。符合条件的区间有 \((x-l) \times (r-x)\) 个。

回到矩阵上,一维情况求的是矩阵的长的取值。宽的上界由枚举确定,下界取值在 \([x,n]\) 内,有 \(n - x + 1\) 种取法。

因此,合法的矩阵总共有 \((n-x+1) \times (y-l) \times (r-y)\) 个。

最后,统计完一个格子的贡献后,要更新该格子对这种颜色的左右区间范围的限制。
代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iomanip>

using namespace std;

const int kmax = 105;

int n, m, c[kmax][kmax];
int p[kmax][kmax * kmax];
int l[kmax], r[kmax];
long long res, resc;

void Calc(int x, int y, int c) {
    for (int i = 1; i <= n + 1; i++) {
        l[i] = 0, r[i] = m + 1; // 初始化左右区间
    }
    for (int i = 1; i < y; i++) {
        l[p[i][c]] = max(l[p[i][c]], i); // 计算左区间
    }
    for (int i = y + 1; i <= m; i++) {
        r[p[i][c]] = min(r[p[i][c]], i); // 计算右区间
    }
    for (int i = x; i > p[y][c]; i--) {
        l[i] = max(l[i], l[i + 1]), r[i] = min(r[i], r[i + 1]); // 边计算边更新
        //		cout << i << ' ' << l[i] << ' ' << r[i] << '\n';
        res += 1ll * (n - x + 1) * (y - l[i]) * (r[i] - y);
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> c[i][j];
        }
    }
    resc = n * (n + 1) / 2 * m * (m + 1) / 2; // 总的矩阵数量
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            Calc(i, j, c[i][j]); // 计算贡献
            p[j][c[i][j]] = i; // 更新限制
        }
    }
    //	cout << res << '\n';
    cout << fixed << setprecision(9) << 1.0 * res / resc << '\n';
    return 0;
}
posted @ 2023-07-14 23:03  ereoth  阅读(80)  评论(1)    收藏  举报