ACM算法:悬线法

悬线法的用途:针对求给定矩阵中满足某条件的极大矩阵,比如“面积最大的长方形、正方形”“周长最长的矩形等等”。可以满足在

时间复杂度为O(M*N)的要求,比一般的枚举高效的多,也易于理解。


悬线法思路:悬线法,悬线的定义,就是一条竖线,这条竖线要满足上端点在整个矩形上边界或者是一个障碍点。然后以这条悬线

进行左右移动,直到移至障碍点或者是矩阵边界,进而确定这条悬线所在的极大矩阵。也就是说,我们要针对矩阵中每个点进行求极

大矩阵的操作,所以我们需要Left[]数组存每个点能到达的最右位置,Right[]数组存放每个点能到达的最左位置,Up[]数组位置。

设置好这些数组之后,我们开始遍历矩阵中的每个点ves[i,j],把每个点和上一个点(ves[i-1][j])的Left和Right进行比较,分别取最大和

最小,Up则是上一个点的Up+1,进而求出面积进行比较。所以我们可以得到相关的递推公式。


递推公式:Up:Up[i][j] = Up[i-1][j] + 1 

Right:min(Right[i][j],RIght[i-1],[j])

Left::max(Left[i][j],Left[i-1][j])



在这里推荐一篇国家队的论文《极大化思想解决最大子矩阵问题》

http://blog.csdn.net/clover_hxy/article/details/50532289?locationNum=1&fps=1

这里全面讲解了极大化矩阵的思想,只要能看透,我相信对于悬线法的理解肯定会更加深入。只是没有例题,所以你进行理解后再

倒回来理解我的例题,印象会更加深刻。

例题解析:

题目:棋盘制作

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源
于易经的思想,棋盘是一个8*8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。而我们的主人公小Q,
正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定
将棋盘扩大以适应他们的新规则。小Q找到了一张由N*M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种
颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。不过小Q还没有决定是找
一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他
希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是小Q找到了即将参加全
国信息学竞赛的你,你能帮助他么?

Input

第一行包含两个整数N和M,分别表示矩形纸片的长和宽。接下来的N行包含一个N * M的01矩阵,表示这张矩形
纸片的颜色(0表示白色,1表示黑色)。

Output

包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋
盘的面积(注意正方形和矩形是可以相交或者包含的)。

Sample Input

3 3
1 0 1
0 1 0
1 0 0

Sample Output

4
6

Hint

N, M ≤ 2000


这是中文题,大家都看得懂,我这就不解释什么了,直接上代码

#include 
#include 
using namespace std;

const int Max = 2005;

int ves[Max][Max], up[Max][Max], Left[Max][Max], Right[Max][Max];
int temp1 = 1, temp2 = 1;

int main(void)
{
	ios::sync_with_stdio(false);
	int N, M;
	cin >> N >> M;
	for(int i = 1; i <= N; i++)
		for (int j = 1; j <= M; j++) {
			cin >> ves[i][j];
			Left[i][j] = Right[i][j] = j;	//初始化Right和Left,使他们值为点所在纵坐标
			up[i][j] = 1;	//初始化up使其值为1
		}

	for (int i = 1; i <= N; i++)
		for (int j = 2; j <= M; j++)
			if (ves[i][j] == 1 - ves[i][j - 1])	//判断相邻两个数是否不同
				Left[i][j] = Left[i][j - 1];	//是,则

	for (int i = 1; i <= N; i++)
		for (int j = M - 1; j > 0; j--)
			if (ves[i][j] == 1 - ves[i][j + 1])
				Right[i][j] = Right[i][j + 1];

	for(int i = 1;i <= N; i++)
		for (int j = 1; j <= M; j++) {
			if (i > 1 && ves[i][j] == 1 - ves[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_instance = Right[i][j] - Left[i][j] + 1;	//计算长度
			int B_instance = min(A_instance, up[i][j]);	//算出长宽中较小的边,以计算正方形
			temp1 = max(temp1, B_instance * B_instance);	//正方形面积
			temp2 = max(temp2, A_instance * up[i][j]);		//长方形面积
		}

	cout << temp1 << endl << temp2 << endl;
}


总结:悬线法也是针对一类问题——求极大矩阵的问题,学会之后又将是解决一类问题的利器,而起思路非常清晰,易于理解。

只是有时候变式比较多,需要多加练习进行熟练。



你可能感兴趣的:(悬线法,ACM,棋盘制作,算法,ACM算法)