[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; }