1. 程式人生 > >【子矩陣】【高效演算法設計】

【子矩陣】【高效演算法設計】

.子矩陣

小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,他選擇的子矩陣的種類數。


輸入樣例
2 3
2 5 1
6 3 4


輸出樣例
6

4

5

1

1

1


資料範圍
對於30%的資料,1<=N, M<=50;
對於全部的資料,1<=N, M<=300。

Solution:

首先我覺得關於這種矩陣的題還是很有必要總結一下的。這是第一道典型:尋找關於某些滿足條件矩陣的資訊(矩陣當然是連續的)

我們先考慮怎麼暴力。

列舉所有的子矩陣:即 列舉四個邊界,然後查詢最小值 複雜度達到了驚人的

O(N6 )。

那麼考慮如何優化這麼暴力的列舉方法

①      列舉的條件太多時,可以考慮部分列舉:參見高效演算法設計。那麼在這道題中,我們可以考慮只列舉上下邊界,列舉完一個上界和下界後。我們對在這之間的所有子矩陣處理。

②      如何處理:對於上下邊界之間的每一列,我們可以求出該列中的最小值。這個可以預處理得到。複雜度N(列舉每一個整列需要N*N) 然後YZ大神說可以用RMQ 快速N*logN搞出來。

③      然後。我們考慮每一列的最小值可以最多向右向左擴充套件的長度。所謂擴充套件的意思是指,當前最小值能影響的最遠的地方。

示意一下。

      |       |      |       |      |        |

      |        |     |        |      |       |

      |       |      |        |     |        |

————————————————————————————-————

1          4      3      6     7        5         2

下面表示該列的最小值。那麼考慮3

向左可以擴充套件到 4(位置2) 向右可以擴充套件到 5(位置6)

那麼記錄 L[3]=2R[3]==6   

那麼L 到R 之間包含了 3 這一列的所有矩陣的最小值都是3!

那麼一共有多少個矩陣呢 答案是 (K-L+1)*(R-K+1) K就是當前位置

用一個單調堆來維護整個區間,就可以高效的算出每個點最遠可以拓展的點。

假設我們先算可以向右拓展多遠。

單調棧中有

1入棧       1

4入棧       1 4

3入棧       1 3  (彈出了4)說明4只能擴充套件到3 這個位置

6入棧       1 3 6

7入棧       1 3 6 7

5入棧       1 3 5 (彈出了6 7,)說明6 7 只能擴充套件到 5 這個位置

2入棧       1 2   (彈出了3 5) 說明 3 5只能擴充套件到 2所在的位置、

然後棧裡面剩下的樹都可以擴充套件到最右邊。

左邊就不說了。

由此可以的出答案。

#include<cstdio>#include<iostream>#include<cstring>#include<stack>using namespace std;const int maxn=305,inf=0x3f3f3f3f;int n,m,s[maxn][maxn],miny[maxn],ans[maxn*maxn],L[maxn],R[maxn];stack<int>q;inline void _read(int &x){ char t=getchar();bool sign=true; while(t<'0'||t>'9') {if(t=='-')sign=false;t=getchar();} for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0'; if(!sign)x=-x; } int main(){ _read(n);_read(m); int i,j,k; for(i=1;i<=n;i++) for(j=1;j<=m;j++)_read(s[i][j]); for(i=1;i<=n;i++){ memset(miny,inf,sizeof(miny)); for(j=i;j<=n;j++){ for(k=1;k<=m;k++)miny[k]=min(miny[k],s[j][k]); for(k=1;k<=m;k++){ while(q.size()&&miny[k]<miny[q.top()]){ R[q.top()]=k-1; q.pop(); } q.push(k); } while(q.size()){ R[q.top()]=m; q.pop(); } for(k=m;k;k--){ while(q.size()&&miny[k]<miny[q.top()]){ L[q.top()]=k+1; q.pop(); } q.push(k); } while(q.size()){ L[q.top()]=1; q.pop(); } for(k=1;k<=m;k++) ans[miny[k]]+=(R[k]-k+1)*(k+1-L[k]); } } for(i=1;i<=n*m;i++) printf("%d\n",ans[i]);}