Problem Description
Dear Liao
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
Input
The first line of input contains an integer T(T≤8) indicating the number of test cases.
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).
Output
Each case outputs a number that holds 9 decimal places.
Sample Input
1
2 3
1 2 1
2 1 2
Sample Output
1.666666667
Hint
6(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的矩阵,每个矩阵元素有一个值w[i][j],现在要求找一个子矩阵的value为子矩阵中不同权重的数量,求子矩阵value的期望;
解题思路:要求所有子矩阵的期望只需找到所有子矩阵中不同权重的数量和子矩阵的数量 ans = (所有子矩阵中不同权重的数量)/(子矩阵的数量);现在只有两个问题了:子矩阵数量和所有子矩阵中不同权重的数量
子矩阵的数量:若有一个n*m的矩阵,则这个矩阵的子矩阵的个数为n*(n+1)m(m+1)/4
所有子矩阵中不同权重的数量:在解决这个问题上要找到每个子矩阵中有多少种颜色(权重),换而言之就是要找每个颜色(权重)包含在多少个不同的子矩阵中。现在我们的任务就是要不重复地计算出每个颜色包含在多少个子矩阵中。首先我们对每个颜色按先行优先在列优先的排序规则排序,在计算子矩阵时把每个子矩阵都算在排序后最先出现的哪一个点上。这样在计算地i的点所包含的矩阵时所求矩阵一定不能包含前1—i-1个点。
我用第一组测试数据来说明我如何找到所有子矩阵中不同权重的数量。
这里先申明一下我们可以用上下左右这四个边界来表示一个矩阵,不同的上下左右边界可以表示一个矩阵。
我们先对1这个颜色的节点排序排序后结果为(1,1)->(1,3)->(2,2)先找包含(1,1)的所有子矩阵,可以找出6个(因为它的上下左右边界没有任何限制);再找包含(1,3)的所有子矩阵这个时候所找的子矩阵一定不能包含(1,1)这个点。而且在找某个点的下边界时,由于我们的点一定是从左到右从上到下排好序的,所以下边界是没有限制的,所以这时我们的下边界有2个选择分别是(l =1,2)对于组测试数据,上边界也就只有一个选择(h =1)因为不能包含(1,1),所以左边界只能是2,右边没有限制所以右边界的选择有两个(r=2,3)。
此时我们就固定上边界,对于固定的一个上边界它所能构成子矩阵的个数为(下边界方案数左边界方案数右边界方案数);
以此类推算出所有的点包含的子矩阵个数
注意这里可以有一个优化:
如果出现这种情况的话第三行第三列和第四行第三列都出现了同一种颜色,而且在计算第三行第三列所包含的矩阵的时候把第四行第三列以ni-1 —1为上边界能包含的所有矩阵都包含,所以这个时候可以直接break,结束此次循环了。
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL w[105][105];//保存原理图
int vis[105],m,n;//vis[j] = i 表示将第j列用i标记
vector<int> y[105];//y[i]用来表示第i行有哪些列被标记过
vector<pair<int,int> > C[10005];//用来保存所有颜色出现的坐标
LL calc(int col)
{
memset(vis,0,sizeof(vis));
LL sum = 0;
for(auto now:C[col]){
int ni = now.first,nj = now.second;//保存当前颜色的坐标
for(int i = 1; i <= m; i++)//对于每次找那些坐标被标记过之间都应该将此数组清零,防止以前数据对这次查找有影响
y[i].clear();
for(int i=1;i<=m;i++)
if(vis[i])
y[vis[i]].push_back(i);
int yl = 1,yr = m,flag = 0;
for(int i = ni;i>=1;i--){//枚举所有可能的上边界
vector<int>::iterator it;
for(it = y[i].begin();it!=y[i].end();it++){//如果第i行有被标记的,找出被标记的列数,并更新左右区间
int yy = *it;
if(yy<nj)
yl = max(yl,yy+1);
else if(yy > nj)
yr = min(yr,yy-1);
else{//如果这个点的正上方有与它颜色相同的点结束循环
flag = 1;
break;
}
}
if(flag) break;
sum += (n-ni+1)*(nj-yl+1)*(yr-nj+1);//计算已ii上上边界的方案数,
}
vis[nj] = ni;//将第nj列标记为ni方边利用y数组更新边界
}
return sum;
}
int main()
{
int T;scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&m);
for(int i=0;i<=n*m;i++)
C[i].clear();
for(int i = 1;i <= n; i++)
for(int j = 1; j <= m; j++){
scanf("%lld",&w[i][j]);
C[w[i][j]].push_back(make_pair(i,j));//记录每种颜色的坐标
}
LL sum = 0;//记录不同颜色子矩阵的个数
for(int i=0;i<=n*m;i++)
if(!C[i].empty())
{
sort(C[i].begin(),C[i].end());//将每种颜色按坐标先行优先再列优先排序
sum += calc(i);
}
LL num = n*(n+1)*m*(m+1)/4;//子矩阵的个数
double ans = (sum*1.0)/num;
printf("%.9lf\n",ans);
}
return 0;
}