题意
给出一个 N×M N×M 的数字矩阵,求其所有子矩阵不同数字个数的期望值。
思路
直接枚举子矩阵的时间复杂度为 O(n4) O(n4) ,显然不能达到预期的效果。
于是我们考虑每种数字对结果的贡献,计算至少包含某一数字的子矩阵有多少个,但因为如此一定会产生重复计数,所以我们要为其限定条件。
- 假设相对的两点 (x1,y1),(x2,y2) (x1,y1),(x2,y2) 可以唯一确定一个矩形,且 x1<=x2,y1<=y2 x1<=x2,y1<=y2
- 对于当前所判断的数字 a a ,其坐标为 (x,y) (x,y)
- 此时, (x1,y1) (x1,y1) 的位置只可以在 (x,y) (x,y) 的左上方, (x2,y2) (x2,y2) 的位置只可以在 (x,y) (x,y) 的右下方(包含当前行)
- 且最终 (x,y) (x,y) 以上以左部分无第二个数字 a a
这样便不会产生重复计数啦~
接下来的工作便是找寻当前位置 (x,y) (x,y) 以上以左的空间中存在多少个可选择点,然后配合其以下以右的空间大小得出结果。
在求整个矩阵的子矩阵总数时,我们可以将其看作分别求出包含各点的子矩阵数,但由于所有子矩阵的边界都相等,所以只固定一个边界会重复计算很多个,所以我们需要固定两个相邻的边界,让每个子矩阵都是这两个边界固定来保证不重复计算,所以我们可以直接将下边界和右边界固定,此时的包含格子 (x,y)
的子矩阵数为 (y-l+1)*(x-u+1)
,由于我们的数组下标都是从0开始,所以 l=0,u=0
,相当于 (y+1)*(x+1)
,既然这样,我们不如直接让下标从1开始往后存,也就是 x*y
,这样的话,总子矩阵数就是各个格子坐标乘积的总和
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=105;
int color[maxn*maxn][maxn];
int l[maxn],r[maxn];
ll ans=0;
int n,m;
void solve(int x,int y,int c)
{
for(int i=1;i<=x;i++)
{
l[i]=0;r[i]=m+1;
}
for(int i=1;i<y;i++)
l[color[c][i]]=i;
for(int i=m;i>y;i--)
r[color[c][i]]=i;
int h=color[c][y];
for(int i=x-1;i>h;i--)
{
l[i]=max(l[i],l[i+1]);
r[i]=min(r[i],r[i+1]);
}
for(int i=x;i>h;i--)
{
ans+=ll(n-x+1)*(r[i]-y)*(y-l[i]);
}
}
int main (){
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
memset(color,0,sizeof(color));
ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
solve(i,j,x);
color[x][j]=i;
}
ll tem=n*(n+1)*m*(m+1)/4;
printf("%.9lf\n",ans*1.0/tem);
}
return 0;
}