To my boyfriend
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 667 Accepted Submission(s): 305
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.666666667Hint6(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)思路:要求所有子矩阵的期望只需找到所有子矩阵中不同权重的数量和子矩阵的数量 ans = (所有子矩阵中不同权重的数量)/(子矩阵的数量);现在只有两个问题了:子矩阵数量和所有子矩阵中不同权重的数量子矩阵的数量:若有一个n*m的矩阵,则这个矩阵的子矩阵的个数为n*(n+1)m(m+1)/4;(神奇的公式)所有子矩阵中不同权重的数量:在解决这个问题上要找到每个子矩阵中有多少种颜色(权重),换而言之就是要找每个颜色(权重)包含在多少个不同的子矩阵中。现在我们的任务就是要不重复地计算出每个颜色包含在多少个子矩阵中。首先我们对每个颜色按先行优先在列优先的排序规则排序,在计算子矩阵时把每个子矩阵都算在排序后最先出现的哪一个点上。这样在计算地i的点所包含的矩阵时所求矩阵一定不能包含前1—i-1个点。因为 在计算地i的点所包含的矩阵时所求矩阵一定不能包含前1—i-1个点。所以计算的时候就要考虑上方的情况。 枚举上界进行计算,根据上界的那一行的情况(有没有包含已经计算过的点)来更新左右边界,对于每一个枚举到的上界,对应的矩形个数就是(n-i+1)*(j-l+1)*(r-j+1)。(下边界*左边界*右边界);优化:画图不难看出,对于每一列,只要标记出这一列最下边的那个点,这一列上方的点就不用再计算。代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#include<bits/stdc++.h>
#define N 110 using namespace std; typedef pair< int, int> P; vector<P> col[N * N]; ///存储所有的点; vector< int> yth[N]; ///保存找过了的点; int ls[N * N], n, m; ///临时存储上一次找过的点,让后放进yth里面; int main() { int T_T; cin >> T_T; while(T_T--) { scanf( "%d%d", &n, &m); memset(col, 0, sizeof(col)); long long ans = 0, num = 0; for( int i = 1; i <= n; i++) { for( int j = 1; j <= m; j++) { int x; scanf( "%d", &x); col[x].push_back(P(i, j)); ///颜色分类存放 } } for( int i = 0; i < m * n; i++) { ///枚举每一种颜色 if (col[i].empty()) continue; sort(col[i].begin(), col[i].end()); ///按照坐标对同种颜色的点排序 memset(ls, 0, sizeof(ls)); for( int j = 0; j < col[i].size(); j++) { ///算每一种颜色的贡献(经过该颜色的每一个矩形) int l = 1, r = m; ///左右边界置为最宽,再慢慢进行维护 bool flag = 1; ///初始化左右界 for( int k = 1; k <= m; k++) ///只需要标记每一列最下边的,上边的清空,进行优化; yth[k].clear(); for( int k = 1; k <= m; k++) ///把每列最底端的点对应的行标记上该列 { if (ls[k]) ///第k列的行为ls[k]; yth[ls[k]].push_back(k); ///这一行多一个点,不能被下一次包含; } int x = col[i][j].first; ///颜色为i的第j个点的坐标的行 int y = col[i][j].second; ///颜色为i的第j个点的坐标的列 for( int k = x; k > 0 && flag; k--) ///行减 枚举上界 { for( int p = 0; p < yth[k].size(); p++) ///枚举该行标记过的点,维护左右边界 { if (yth[k][p] < y) ///第k行,第p个点的 列 坐标。 l = max(l, yth[k][p] + 1); else if(yth[k][p] > y) r = min(r, yth[k][p] - 1); else { flag = 0; break; } ///如果找到一个与当前点同列的已更新点则退出 } if (!flag) break; // printf("%d %d %d ",n-x+1,y-l+1,r-y+1); //printf("%d***\n",(n-x+1)*(y-l+1)*(r-y+1)); ans += (n - x + 1) * (y - l + 1) * (r - y + 1); ///统计当前上界对应方案数 } ls[y] = x; ///第y列的,最下边点的所在的行 x // printf("*********************\n"); } } num = n * (n + 1) * m * (m + 1) / 4; printf( "%.9f\n", ( double)ans / num); ///输出期望 } return 0; } |