矩阵颜色

Problem

一个 n×m 的矩阵,第 i 行第 j 列元素有一个颜色 ci,j,求所有子矩阵的颜色种类数的平均值。

n,m100,ci,jn×m

Input

第一行两个正整数 nm
接下来 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],有 xl 种取值;右端点范围在 [x,r),有 rx 种取值。符合条件的区间有 (xl)×(rx) 个。

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

因此,合法的矩阵总共有 (nx+1)×(yl)×(ry) 个。

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

#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 @   ereoth  阅读(57)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示