17暑假多校联赛2.8 HDU 6052 To my boyfriend

To my boyfriend

Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 32768/32768 K (Java/Others)

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)


题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6052

分析

题意:给定一个n*m的矩阵,每个格子有一个数字,每个数字代表一种颜色,这个矩阵的子矩阵的价值是该子矩阵中不同颜色的数目,计算这个矩阵所有子矩阵(包括自身)的期望
期望=所以有子矩阵的价值总和/子矩阵总数目;
包含格子 (x,y)的子矩阵数 sum 的求法:
  设 l 为它的左边界 r 为它的左右边界 u 为它的左上边界 d 为它的下边界
  我们需要先固定一个边界,固定的边界为该格子的位置,才能求出这边所对的方向的子矩阵数
  比如,我们令上边界固定,则上边界以下可以形成的子矩阵总数为 (r-l+1)*(d-x+1)
  同理可求出其他几个方向的子矩阵数目,但此时会有重复,每个部分都被算了2遍,所以在求以包含某个格子的子矩阵数时,为了避免重复,可以只将相对的两个边界分别固定到该格子的位置,再用另三个边界求得子矩阵数,本题中,是分别固定上下边界来求满足的总数
  在求整个矩阵的子矩阵总数时,我们可以将其看作分别求出包含各点的子矩阵数,但由于所有子矩阵的边界都相等,所以只固定一个边界会重复计算很多个,所以我们需要固定两个相邻的边界,让每个子矩阵都是这两个边界固定来保证不重复计算,所以我们可以直接将下边界和右边界固定,此时的包含格子 (x,y)的子矩阵数为 (y-l+1)*(x-u+1) ,由于我们的数组下标都是从0开始,所以 l=0,u=0 ,相当于 (y+1)*(x+1) ,既然这样,我们不如直接让下标从1开始往后存,也就是 x*y ,这样的话,总子矩阵数就是各个格子坐标乘积的总和

问:肯定有人不明白为什么固定上边界之后,所对的方向的子矩阵个数为 (r-l+1)*(d-x+1)
其实包含某个格子的子矩阵个数=该格子所有边界中所包含的格子总数是相等的,不信的话可以自己画一个矩阵数一下
问:那为什么求子矩阵总数时需要每个格子的x,y的乘积的总和呢?
因为刚才所说的,是包含了该格子的子矩阵数,还有不包含该格子的子矩阵,所以要所有的求和
问:那又为什么求子矩阵总数时需要两个相邻的边界都固定为该格子的位置呢?
因为上个问题说的是不包含该格子的子矩阵总数,所以,在求的时候,求过的格子就不能再包含在子矩阵内,所以需要固定两个相邻的边界,而固定任两个相邻的边界的结果都是相同的,而我们固定下边界和右边界计算的时候最为方便

注意:上面提到的让边界固定,意思都是让边界为该格子所在的行或者列

刚才我们也说了为了避免重复,计算过的格子就不能再去给别的格子用来计算,所以我们在计算子矩阵的价值时也要考虑到它
问:我们所有子矩阵的价值总和怎么算呢?
我们可以让每个格子的颜色认作他所有子矩阵的唯一颜色,也就是相当于,计算包含该格子的子矩阵总数,每个子矩阵的价值都为1,然后再计算下一个格子的,往后便遍历的时候,需要考虑已经遍历过的格子中与正在计算的格子颜色相同的格子,不同颜色的格子来重复计算同一个子矩阵可以保证该子矩阵的真正的价值不会少计算,相同颜色的话就不能重复计算,因为我们是先上后下,先左后右来遍历的,所以右边界和下边界都为整个矩阵的边界,而上边界和左边界需要考虑颜色相同的格子的位置来确定
问:为什么不同颜色的格子来重复计算同一个子矩阵可以保证该子矩阵的真正的价值不会少计算,相同颜色的话就不能重复计算
比如一个子矩阵是2*2,它的真正价值为3,也就是有三种颜色,所以每种颜色都只计算这个矩阵1次就能等于它的真正价值,如果相同的那个颜色再算一遍的话,就会导致计算值高于真正值

计算方法和思想就是这些了,看懂这些代码很好理解

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
int num[maxn][maxn];
int n,m;
ll cal(int x, int y)
{
    ll res=0;
    int c=num[x][y],L=1,R=m;
    for (int i=x; i>=1; i--)///来确定上边界
    {
        if(i<x&&num[i][y]==c)break;
        int l=y,r=y;
        for(int j=y-1; j>=max(1,L); j--)///来确定左边界
        {
            if(num[i][j]==c)break;
            ///因为是先上后下,先左后右遍历
            ///所以为了避免子矩阵重复,已经遍历过的同色的点不能再用
            ///右边界同理
            l=j;///不同的话就可以做为边界
        }
        L=max(L,l);
        ///左边界会受上一个上边界的左边界的影响
        ///左边界如果大于上一个上边界的左边界,组成的将不是矩阵,而是L型
        ///右边界同理
        if(i==x)
        ///如果上边界在点(x,y)的行时,即x行,右边界不受限,直接为整个矩阵的右边界
        ///此时只用算出左边界就可以求子矩阵数
        {
            res+=(ll)(n-x+1LL)*(y-L+1LL)*(R-y+1LL);///加上此时的子矩阵数
            continue;
        }
        for(int j=y+1; j<=min(m,R); j++)///来确定右边界
        {
            if(num[i][j]==c)break;///同左边界
            r=j;///不同的话就可以做为边界
        }
        R=min(R,r);///同左边界
        res+=(ll)(n-x+1LL)*(y-L+1LL)*(R-y+1LL);///加上此时的子矩阵数
    }
    return res;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&n,&m);
        ll cnt=0;///存放子矩阵总数
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                cnt+=(i*j);
                scanf("%d",&num[i][j]);
            }
        }
        ll ans=0;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                ans+=cal(i,j);///计算整个矩阵价值总和
            }
        }
        printf("%.9f\n",ans*1.0/cnt);
        ///价值总和除以总个数等于期望
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值