To my boyfriend
I never forget the moment I met with you. You carefully asked me: "I have a very difficult problem. Can you teach me?". I replied with a smile, "of course". You replied:"Given a matrix, I randomly choose a sub-matrix, what is the expectation of the number of **different numbers** it contains?"
Sincerely yours,
Guo
Each case contains two integers, n and m (1≤n, m≤100), the number of rows and the number of columns in the grid, respectively.
The next n lines each contain m integers. In particular, the j-th integer in the i-th of these rows contains g_i,j (0≤ g_i,j < n*m).
1 2 3 1 2 1 2 1 2
1.666666667
Hint6(size = 1) + 14(size = 2) + 4(size = 3) + 4(size = 4) + 2(size = 6) = 30 / 18 = 6(size = 1) + 7(size = 2) + 2(size = 3) + 2(size = 4) + 1(size = 6)
题意:
给出一个n*m的矩阵,每个1*1的小矩阵中都有一个数字,现在求大矩阵中所有的矩阵所包含的数字种类和,再除以所有的矩阵数,求数字的期望。
思路:
我们假设一个数字代表一种颜色,便于下面描述。
首先介绍一种求大矩阵中所有矩阵个数的方法。
一个n*m的矩阵,它所包含的所有矩阵个数为:
int total=0;
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
total=total+i*j;
此公式代表,以(i,j)这个小矩阵块为右下角的矩阵共有多少个,即从前i行中找一行作为大矩阵的上边,从前j列中找一列作为大矩阵的左列,这样所形成的大矩阵有多少种,即C(i,1)*C(j,1)即i*j。遍历矩阵的每一个1*1小矩阵,就可得n*m的矩阵总个数。
下面介绍如何求出大矩阵中所有的矩阵所包含的颜色种类和。我们以一种颜色为例,这种颜色对答案的贡献为,所有包含这种颜色的矩形个数。我们遍历这种颜色的每一个1*1小矩阵,找出包含这个小矩阵的所有矩阵,就可以得出这个小矩阵对答案的贡献,再遍历所有该颜色的小矩阵,相加就可以得出该颜色对答案的贡献。但是这样直接算会产生重复。所以我们探讨消除这种重复的方法。
首先,我们遍历时找的是,有多少个矩阵包含这个小矩阵,也就是说,有多少个矩阵至少包含一种这样的颜色,现在我们只要保证每次遍历都不与之前的重复,我们就可以消除重复。所以我们定义一种模式,即遍历每一个1*1小矩阵时,我们只能包含这个小矩阵以下,以右的同颜色矩阵,而这个小矩阵以左,以上的同颜色矩阵不再包含,这样就可以实现每次计算时都不重复。这样我们就转化成为寻找边界,即为寻找遍历该小矩阵时,我们所能计算的矩阵的边界。我们在之前定义的方法的限制下,找到每一个小矩阵所形成的上边界,下边界,左边界,有边界,其中这四条边界每一条都是不固定的。其中,由于上边界和下边界的取值范围固定,(设该小矩阵的位置是(x,y),则上边界的取值范围是1到x,下边界的取值范围是x到n),所以我们主要是通过枚举上边界,去限制左右边界。如何找边界及找到边界如何计算详见代码注释。
下面贴上代码:(具体实现过程详见注释)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=105;
int n, m;
int color[MAXN][MAXN];
int count(int x, int y)
{
LL ans=0;
int c=color[x][y]; //c是颜色号
int L=1,R=m; //左边界初始化为1,右边界为m
for (int i=x; i>=1; i--) //把每一次上边界确定为i,根据上边界限制左右边界
{
if (i<x && color[i][y]==c)
break;
//遍历到第i行,若i<x && color[i][y]==c则说明[x][y]的正上方有一个同颜色的矩阵
//此时无论我们怎样确定左右边界,由于下边界为x到n,我们所得出的矩阵都一定会包含[i][y]这个矩阵
//而根据记下记右,不记上不记左的原则,我们不应包含[i][y]这个小矩阵,所以直接break
int l=y,r=y; //左右边界均从当前列的位置左右移动
for (int j=y-1; j>=max(1,L); j--)
{
if (color[i][j]==c) //从当前列数向左找,找到i行第一个同颜色的方块,记录下左边界
break;
l=j;
}
L=max(L,l);
if (i==x) //如果i为当前行,(即上边界为x),则只需要找左边界,右边界为m(因为可以包括右边和下边的相同颜色方块)
{
ans=ans+(LL)(n-x+1LL)*(y-L+1LL)*(R-y+1LL);
continue;
}
for (int j=y+1; j<=min(m,R); j++)
{
if (color[i][j]==c) //从当前列数向右找,找到i行第一个同颜色的方块,记录下右边界
break;
r=j;
}
R=min(R,r);
ans=ans+(LL)(n-x+1LL)*(y-L+1LL)*(R-y+1LL);
//这个公式代表,从x到n中选取一行作为所选矩形的下边,所选矩形的上边为第i行
//从L到y中选一列作为所选矩形的左边,从y到R中选一列作为矩形的右边
//四条线所圈出的矩形就是所选矩形
}
return ans;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
scanf("%d",&color[i][j]);
LL ask=0,total=0;
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
ask=ask+count(i,j);
total=total+i*j; //计算n*m矩阵有多少个小矩阵,简单算法
}
}
printf("%.9f\n",ask*1.0/total);
}
return 0;
}