1. 程式人生 > >洛谷 U360 子矩陣 (NOIP模擬賽T1)題解

洛谷 U360 子矩陣 (NOIP模擬賽T1)題解

題解 實現 oid ac代碼 格式 memset algorithm min ons

題目鏈接:https://www.luogu.org/problem/show?pid=U360

題目背景

夏令營

題目描述

小A有一個N×M的矩陣,矩陣中1~N*M這(N*M)個整數均出現過一次。現在小A在這個矩陣內選擇一個子矩陣,其權值等於這個子矩陣中的所有數的最小值。小A想知道,如果他選擇的子矩陣的權值為i(1<=i<=N×M),那麽他選擇的子矩陣可能有多少種?小A希望知道所有可能的i值對應的結果,但是這些結果太多了,他算不了,因此他向你求助。

輸入輸出格式

輸入格式:

第一行,兩個整數N, M。

接下來的N行,每行M個整數,表示矩陣中的元素。

輸出格式:

N×M行,每行一個整數,其中第i行的整數表示如果小A選擇的子矩陣權值為i,他選擇的子矩陣的種類數。

輸入輸出樣例

輸入樣例#1:
2 3
2 5 1
6 3 4
輸出樣例#1:
6
4
5
1
1
1

分析:
  這題是聽WZY大佬和DYZ大佬講的。
  猛一看這題好像可以用暴力,但是顯然矩陣並不是一個規則的矩形,暴力枚舉邊界的後果就是絕對超時。
  考慮使用部分枚舉掃描列、單調棧。
  首先我們假想兩條線,就是子矩陣的上下邊界,用兩個for循環從上往下掃。
  一開始,這兩條線是重合的,都在矩陣的第一行。此時我們記錄下每一列的值,作為子矩陣在某一列的最小值(因為此時的子矩陣只有這一行,也就是每列只有一個數)。

  然後把一條線往下挪,每次挪一行,用新的行中每一列的數與“最小值”作比較,更新“最小值”。(遞推求最小值)
  這時候兩條線之間就形成了一個空間,選擇每一列的當前的最小值,向左右方向沿直線擴展,直到遇到一個比它更小的數為止,這時它的上、下、左、右邊界就圍成了一個矩陣。記錄下此時對於這個最小值生成的子矩陣的數量(公式:(K-L+1)*(R-K+1) , 其中K為當前選擇的列,L為往左擴展最遠到達的列,R為往右擴展最遠到達的列)。
  上面的計算用單調棧來實現。
  同理繼續向下遞推,可以計算出每一個點的子矩陣數。隨著邊界的移動,同一個點可能會被計算多次,也就是說這個矩陣不是一個標準的矩形,因此最後應該把每一個點計算出的多個結果相加,得到正確結果。

AC代碼(由WZY大佬提供):

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cstring>
 5 #include <algorithm>
 6 inline void read(int &x){x = 0;char ch = getchar();char c = ch;while(ch > 9 || ch < 0)c = ch, ch = getchar();while(ch <= 9 && ch >= 0)x = x * 10 + ch - 0,ch = getchar();if(c == -)x = -x;}
 7 inline void swap(int &a, int &b){int tmp = a;a = b;b = tmp;}
 8 inline int max(int a, int b){return a > b ? a : b;}
 9 inline int min(int a, int b){return a < b ? a : b;}
10 
11 const int INF = 0x3f3f3f3f;
12 const int MAXN = 5000 + 10;
13 const int MAXM = 100000;
14 
15 int n,m,num[MAXN][MAXN];
16 int stack[MAXM], top;
17 int mi[MAXM];
18 int L[MAXM],R[MAXM];
19 int ans[MAXM];
20 
21 int main()
22 {
23     read(n);read(m);
24     for(int i = 1;i <= n;++ i)
25         for(int j = 1;j <= m;++ j)
26             read(num[i][j]);
27     //枚舉上面的行i
28     for(register int i = 1;i <= n;++ i)
29     {
30         //註意清為最大值 
31         memset(mi, 0x3f, sizeof(mi));
32         
33         //枚舉i以下的行j 
34         for(register int j = i;j <= n;++ j)
35         {
36              //求得行[i,j]範圍內的每一列的最小值 
37             for(register int k = 1;k <= m;++ k)
38                 mi[k] = min(mi[k], num[j][k]);
39                 
40                 
41             //正向掃描求R,維護一個遞增(或相等)單調棧
42             for(register int k = 1;k <= m;++ k)
43             {
44                 while(top && mi[k] < mi[stack[top]])
45                 {
46                     R[stack[top]] = k - 1;
47                     -- top;
48                 }
49                 stack[++top] = k;
50             }
51             while(top)
52             {
53                 R[stack[top]] = m;
54                 -- top;
55             }
56             
57             //反向掃描求L,維護一個遞增(或相等)單調棧
58              for(int k = m;k >= 1;k --)
59              {
60                  while(top && mi[k] < mi[stack[top]])
61                  {
62                      L[stack[top]] = k + 1;
63                     -- top;
64                  }
65                  stack[++top] = k;
66              }
67              while(top)
68              {
69                  L[stack[top]] = 1;
70                  -- top;
71              }
72              
73              //掃描列,累加答案 
74              for(register int k = 1;k <= m;k ++)
75              {
76                  ans[mi[k]] += (k - L[k] + 1) * (R[k] - k + 1);
77              } 
78         }
79     }
80     register int tmp = n * m;
81     for(register int i = 1;i <= tmp;++ i)
82     {
83         printf("%d\n", ans[i]);    
84     }
85     return 0;
86 }

洛谷 U360 子矩陣 (NOIP模擬賽T1)題解