1. 程式人生 > >[ZJOI2007]棋盤製作 懸線法dp 求限制下的最大子矩陣

[ZJOI2007]棋盤製作 懸線法dp 求限制下的最大子矩陣

https://www.luogu.org/problemnew/show/P1169

 

第一次聽說到這種dp的名稱叫做懸線法,聽起來好厲害

題意是求一個矩陣內的最大01交錯子矩陣,開始想的是dp[2000][2000][2]維護這個位置向上向左擴充的矩陣最大長度之後n²掃一遍,但是寫起來發現並不能有效的擴充,也就是狀態轉移方程很難寫出來。

後來發現有一種奧妙重重的方法叫做懸線法,把我原本向左向上擴充的過程改為記錄每一個點向左向右向上的最大長度,這些狀態很顯然可以通過掃一遍的方法求出來,然後對於每一個點,寬度就是l - r + 1,顯然對於同一個合法區間內的點,他的left和right是相同的。

用自上而下的方法遞推出到N這一行時這個點向上擴充的最大長度之後遞推即可。

懸線法對一類限制下求子矩陣的問題很好用。

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include 
<cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using namespace std; inline int read(){int now=0;register char c=getchar();for(;!isdigit(c);c=getchar()); for(;isdigit(c);now=now*10+c-'0',c=getchar());return now;} #define For(i, x, y) for(int i=x;i<=y;i++) #define
_For(i, x, y) for(int i=x;i>=y;i--) #define Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Sca2(x,y) scanf("%d%d",&x,&y) #define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d\n", x) #define Prl(x) printf("%lld\n",x); #define CLR(u) for(int i=0;i<=N;i++)u[i].clear(); #define LL long long #define ULL unsigned long long #define mp make_pair #define PII pair<int,int> #define PIL pair<int,long long> #define PLL pair<long long,long long> #define pb push_back #define fi first #define se second typedef vector<int> VI; const double eps = 1e-9; const int maxn = 2010; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; int N,M,K; int Left[maxn][maxn],Right[maxn][maxn],up[maxn][maxn]; int MAP[maxn][maxn]; int main() { Sca2(N,M); for(int i = 1; i <= N ; i ++){ for(int j = 1; j <= M ; j ++){ Sca(MAP[i][j]); Left[i][j] = Right[i][j] = j; up[i][j] = 1; } } for(int i = 1; i <= N ; i ++){ for(int j = 2; j <= M ; j ++){ if(MAP[i][j] != MAP[i][j - 1]){ Left[i][j] = Left[i][j - 1]; } } for(int j = M - 1; j >= 1; j --){ if(MAP[i][j] != MAP[i][j + 1]){ Right[i][j] = Right[i][j + 1]; } } } int ans1 = 0,ans2 = 0; for(int i = 1; i <= N ; i ++){ for(int j = 1; j <= M ; j ++){ if(i > 1 && MAP[i][j] != MAP[i - 1][j]){ Left[i][j] = max(Left[i][j],Left[i - 1][j]); Right[i][j] = min(Right[i][j],Right[i - 1][j]); up[i][j] = up[i - 1][j] + 1; } int a = Right[i][j] - Left[i][j] + 1; int b = min(a,up[i][j]); ans1 = max(ans1,b * b); ans2 = max(ans2,a * up[i][j]); } } Pri(ans1); Pri(ans2); return 0; }