P1169 [ZJOI2007]棋盘制作(悬线法)

昨天做了做杭电多校联赛有一道关于悬线法的题,看到这个洛谷推荐悬线法的题目补一篇题解吧
在这里插入图片描述
在这里插入图片描述
这道题跟杭电多校的那道题感觉差不多,但是有一点区别就是要求矩阵内黑白相间,所以我们在计算高度的时候注意一下颜色即可。

1、对矩阵做合法标记处理

首先我们先对所给矩阵的颜色进行一个处理:(0,0)设置为1
如果这一行是第一行,那么如果(i,j)的颜色跟(i,j-1)的颜色不同,那么说明合法,则标记与(i,j-1)相同,反之则取非;
如果这一列不是第一行,那么就跟他的上一行的同一列的颜色作比较,如果(i,j) 的颜色与(i-1,j)的颜色不同,则合法,标记与(i-1,j)相同,反之取非;

for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(i==1){//第一列
				if(j==1) up[i][j] = 1;
				else if(map[i][j]!=map[i][j-1]) {//比较前一列的颜色
					up[i][j] = up[i][j-1];
				}
				else up[i][j] = !up[i][j-1];
			}
			else{//不是第一列,比较同列上一行的颜色
				if(map[i][j]!=map[i-1][j]) up[i][j] = up[i-1][j];
				else up[i][j] = !up[i-1][j];
			}
		}
	}

2、悬线法

与杭电多校不一样的地方多了一个对标志的判断
杭电多校的悬线法:

while(tot && up[i][queue[tot]]>up[i][j]){ //up[i][j]:(i,j) 为底的这一列的高度
……
}
如果这个悬线的高度小于等于上一条悬线的高度,那么统计以上一条边为高建立一个子矩阵,矩阵的宽度一个个往前遍历查找即可

ans=max(ans,(j-queue[tot-1]-1)*up[i][queue[tot]]);
这里需要注意的是矩阵的宽度是再往前推一个点的列+1,因为要统计以上一条边为最小高度的子矩阵,所以高是上一条边的长度,可能有些拗口不太好理解,因为queue[i]中存放的是当前行按照列的高度进行排列的(不改变列的先后顺序),而且是单调不减的,所以第queue[tot-1]列的高度要小于等于第queue[tot]列的高度,所以最大子矩阵的开头就是queue[tot-1]+1(因为有可能这一列的高度小于第tot列,所以不算这一列,所以列数+1也就是从下一列开始),所以子矩阵的宽度就是j-(queue[tot-1]+1),高就是up[i][queue[tot]]。

最后需要注意的是我们没有以最后一列作为最小高度进行子矩阵的查找,所以我们找到第m+1列,因为这一列本身就不存在,所以高度为0,所以就能对以第m列作为最小高度进行一个查找处理,保证情况没由漏掉。

for(int i=1;i<=n;i++){//遍历每一行每一列的点
	int tot=0;
	up[i][m+1]=0;//最后对最后的边进行一个最大子矩阵的查找
	for(int j=1;j<=m+1;j++){//注意是m+1
	    while(tot && up[i][queue[tot]]>up[i][j]){
	    //如果这个悬线的高度小于等于上一条悬线的高度,那么统计以上一条边为高建立一个子矩阵,矩阵的宽度一个个往前遍历查找即可
	        ans=max(ans,(j-queue[tot-1]-1)*up[i][queue[tot]]);
	        //这里需要注意的是矩阵的宽度是再往前推一个点的列+1,高是上一条边的长度,可能有些拗口不太好理解
	        tot--;
	    }
	    queue[++tot]=j;
	 }
}

本题的悬线法:

for(int i=1;i<=n;i++){
		h[i][m+1] = 0;
		int tot = 0;
		for(int j=1;j<=m;j++){//取改行每列的最大高度
			if(i == 1){
				h[i][j] = 1;
			}
			else{
				if(up[i][j] == up[i-1][j]) h[i][j] = h[i-1][j] + 1;
				else h[i][j] = 1;
			}
		}
		for(int j=1;j<=m+1;j++){
			while(tot && (up[i][que[tot]] != up[i][j] || h[i][que[tot]]>h[i][j])){
			//因为有两种情况应该对上一列的情况进行统计,一种是标记变换,一种是这一列的高度小于上一列,所以在这里加一种情况即可
				if(up[i][que[tot]] != up[i][que[tot-1]+1]) break;
				//棋盘边界出现变换,不能加到里面,所以break退出
				int wide = j - que[tot-1] - 1;//棋盘的宽度
				ans = max(ans,wide * h[i][que[tot]]);
				if(wide == h[i][que[tot]]) square = max(square,wide * wide);//加一种统计正方形的情况
				tot--; 
			}
			que[++tot] = j;
		}
	}

其实实质上比杭电多校的悬线法就多了一个标志的判断和正方形的判断,思路跟杭电多校的几乎没什么区别,完整代码入下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 2010;
int map[maxn][maxn];
int up[maxn][maxn],Left[maxn][maxn],Right[maxn][maxn];
int que[maxn];
int h[maxn][maxn];
int square = 0;
int main(){
	int n,m;
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin >> map[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(i==1){
				if(j==1) up[i][j] = 1;
				else if(map[i][j]!=map[i][j-1]) {
					up[i][j] = up[i][j-1];
				}
				else up[i][j] = !up[i][j-1];
			}
			else{
				if(map[i][j]!=map[i-1][j]) up[i][j] = up[i-1][j];
				else up[i][j] = !up[i-1][j];
			}
		}
	}
	
	int ans = 0;
	for(int i=1;i<=n;i++){
		h[i][m+1] = 0;
		int tot = 0;
		for(int j=1;j<=m;j++){
			if(i == 1){
				h[i][j] = 1;
			}
			else{
				if(up[i][j] == up[i-1][j]) h[i][j] = h[i-1][j] + 1;
				else h[i][j] = 1;
			}
		}
		for(int j=1;j<=m+1;j++){
			while(tot && (up[i][que[tot]] != up[i][j] || h[i][que[tot]]>h[i][j])){
				if(up[i][que[tot]] != up[i][que[tot-1]+1]) break;
				int wide = j - que[tot-1] - 1;
				ans = max(ans,wide * h[i][que[tot]]);
				if(wide == h[i][que[tot]]) square = max(square,wide * wide);
				tot--; 
			}
			que[++tot] = j;
		}
	}
//	cout << "------" << endl;
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout << up[i][j] << " ";
//		}
//		cout << endl;
//	}
//	cout << "------" << endl;
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=m;j++){
//			cout << h[i][j] << " ";
//		}
//		cout << endl;
//	}
	cout << square << endl;
	cout << ans << endl;
	return 0;
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值