極大子矩陣——懸線法總結
阿新 • • 發佈:2020-10-10
定義
顧名思義,就是從每一個點(或者邊界)開始,以此為邊界,開始像用一根豎線一樣不斷移動,在遇到障礙點或邊界時確定出極大子矩陣。
核心內容就是確定一條邊界,不斷擴充套件並修改其他邊界
可能有點抽象,做題理解一下就好。
在 wzk 大佬的國家隊論文淺談用極大化思想解決最大子矩陣問題中有詳細的講解
從題目入手講解
例題
本人在洛谷建了一個有關懸線法的題單,可以拿這些題入門-->懸線法合集
棋盤製作
題目連結
思路分析
- 基本上是懸線法板子了。
- 依據懸線法基本思路,我們對於每個點 \((i,j)\) 開三個陣列:\(left[i][j]\),\(right[i][j]\),\(up[i][j]\)
- 而這道題擴充套件的時候判斷一下點值相不相同就好了,這樣就保證了01交錯。
- 正方形一定包含在矩形裡面,每次取矩形的較短邊就好了
\(Code\)
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define R register #define N 2020 using namespace std; inline int read(){ int x = 0,f = 1; char ch = getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,m,a[N][N],l[N][N],r[N][N],up[N][N];//l,r代表每個點向左(右)擴充套件到的最遠位置,up表示向上擴充套件的高度 int ans1,ans2; int main(){ n = read(),m = read(); for(R int i = 1;i <= n;i++){ for(R int j = 1;j <= m;j++)a[i][j] = read(),l[i][j] = r[i][j] = j,up[i][j] = 1; } for(R int i = 1;i <= n;i++){ for(R int j = 2;j <= m;j++){ if(a[i][j]!=a[i][j-1])l[i][j] = l[i][j-1];//在當前行內擴充套件左界 } } for(R int i = 1;i <= n;i++){ for(R int j = m-1;j;j--){ if(a[i][j]!=a[i][j+1])r[i][j] = r[i][j+1];//在當前行內擴充套件右界 } } for(R int i = 1;i <= n;i++){ for(R int j = 1;j <= m;j++){ if(i>1&&a[i][j]!=a[i-1][j]){//向上擴充套件 l[i][j] = max(l[i][j],l[i-1][j]);//左邊界和右邊界需要縮小範圍就縮小,保證矩陣是極大的同時也是合法的,應該比較好理解 r[i][j] = min(r[i][j],r[i-1][j]); up[i][j] = up[i-1][j]+1; } int length = r[i][j]-l[i][j]+1; int width = min(length,up[i][j]) ; ans1 = max(ans1,width*width); ans2 = max(ans2,length*up[i][j]); } } printf("%d\n%d\n",ans1,ans2); return 0; }
奶牛浴場
題目連結
思路分析
- 這題的特殊之處在於,正常的懸線法是沒法做的,因為你陣列開不下這麼大,所以需要從點來入手
- 而從點入手的話,在不斷尋找極大子矩陣的時候可能會出現許多不同的情況,而這些情況都需要進行特殊處理
- 首先依據懸線法的思路,我們將所有點按橫座標排序(注意這題是座標系,而不是矩陣的幾行幾列),然後以每個點為左(右)邊界,向右(左)不斷尋找極大子矩陣,在此過程中依據經過的點的縱座標修改矩陣的上界和下界。
- 這時很有可能出現矩陣的另一個左右邊界到頭了的情況,所以最後再更新一下答案
- 然後就是如果遇到縱座標相同的點怎麼辦,其實這時候直接跳過即可,因為如果縱座標相同,我們修改上界還是下界都是可行的,而無論是修改下界還是上界,都是會在縱座標相同的點的掃描過程中再次被掃描到的。
- 上面的過程都是列舉每個點為左(右)邊界,而還有一種情況就是矩陣的左右邊界都到頭了(比如樣例的答案),這時候我們在掃描的時候是無法掃描到的,所以最後再將所有點按縱座標排序,排完序後相鄰的縱座標不同的兩點之間是沒有其它點的,這時候就會出現以這兩點為上下界,整個牛場的左右兩端為左右邊界的極大矩陣。
- 還有就是要將矩陣的四個頂點人為新增進去,因為邊緣顯然也有極大矩陣
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 5010
#define R register
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int l,w,n,ans;
struct data{
int x,y,id;
}a[N];
inline bool cmp1(data a,data b){
return a.x==b.x ? a.y < b.y : a.x < b.x;
}
inline bool cmp2(data a,data b){
return a.y==b.y ? a.x < b.x : a.y < b.y;
}
int main(){
l = read(),w = read();
n = read();
for(R int i = 1;i <= n;i++)a[i].x = read(),a[i].y = read();
a[++n] = data{0,0},a[++n] = data{0,w},a[++n] = data{l,0},a[++n] = data{l,w};//人為加點
sort(a+1,a+1+n,cmp1);
for(R int i = 1;i <= n;i++){
int up = w,down = 0,len = l-a[i].x,flag = 0;//先向右掃,up、down表示上下界,len表示這個點到牛場右邊界的長度,flag表示遇到縱座標相同的點
for(R int j = i+1;j <= n;j++){
if(a[j].y>=down&&a[j].y<=up){
if(len*(up-down)<=ans)break;
ans = max(ans,(up-down)*(a[j].x-a[i].x));//因為全是座標,所以千萬別加1……
if(a[i].y==a[j].y){
flag = 1;break;//遇到縱座標相同的點直接退出
}
if(a[j].y>a[i].y)up = min(up,a[j].y);//更新上下界
else down = max(down,a[j].y);
}
}
if(!flag)ans = max(ans,len*(up-down));//如果中途退出就不能再更新了,因為上下界沒有更新完
up = w,down = 0,len = a[i].x,flag = 0;//向左掃,同理
for(R int j = i-1;j;j--){
if(a[j].y>=down&&a[j].y<=up){
if(len*(up-down)<=ans)break;
ans = max(ans,(up-down)*(a[i].x-a[j].x));
if(a[i].y==a[j].y){
flag = 1;break;
}
if(a[j].y>a[i].y)up = min(up,a[j].y);
else down = max(down,a[j].y);
}
}
if(!flag)ans = max(ans,len*(up-down));
}
sort(a+1,a+1+n,cmp2);//上面說的左右邊界均在牛場邊界的情況
for(R int i = 1;i < n;i++)ans = max(ans,(a[i+1].y-a[i].y)*l);
printf("%d\n",ans);
return 0;
}