HDU 6957 Maximal submatrix(悬线法 || 优先队列 || 单调栈 )

问题

HDU 6957 Maximal submatrix - https://acm.hdu.edu.cn/showproblem.php?pid=6957

概述

悬线法、优先队列、单调栈的共同点是遍历所有可行矩形区域,不漏不重(或者很少重复)

悬线法

分析

  • 矩形区域特征量:每个可行的矩形区域有4个特征量,即上、下、左、右4个边界

  • 方格特征量:每个方格有3个特征量,即在同列中向上扩展的最大高度,以及在扩展高度最大时向左向右能扩展到的位置(假设不可向下扩展,而且优先向上方扩展,然后向两侧扩展)

  • 特征量的关系:矩形区域特征量可以转化为该矩形区域底边上的某个或某几个方格的特征量;

  • 对应关系:一个矩形区域可能对应多个特征方格,一个方格仅对应了一个矩形区域;遍历方格就能遍历所有可行矩形区域
    在这里插入图片描述

  • 方格特征量数组: h [ i , j ] , l [ i , j ] , r [ i , j ] h[i,j],l[i,j],r[i,j] h[i,j]l[i,j]r[i,j],即 [ i , j ] [i,j] [i,j] 位置的方格的特征量

  • 方格数量 n ∗ m n*m nm,每个方格扩展的复杂度是 O ( 1 ) O(1) O(1),因此复杂度 O ( n ∗ m ) O(n*m) O(nm)

  • 递推(区域合并)关键: h [ i ] [ j ] h[i][j] h[i][j] 根据 h [ i − 1 ] [ j ] , l [ i ] [ j ] h[i-1][j],l[i][j] h[i1][j]l[i][j] 根据行内左侧的最近递减位置及 l [ i − 1 ] [ j ] , r [ i ] [ j ] l[i-1][j],r[i][j] l[i1][j]r[i][j] 根据行内右侧的最近递减位置及 r [ i − 1 ] [ j ] r[i-1][j] r[i1][j]

    • 高度递推: [ i , j ] [i,j] [ij] 位置要么不可向上扩展,那么 h [ i ] [ j ] = 1 h[i][j] = 1 h[i][j]=1 ;要么可以向上扩展,向上能扩展的最大高度只需要看 [ i − 1 , j ] [i-1,j] [i1j] 位置的最大扩展高度即可,不需要遍历上方的位置,此时 h [ i ] [ j ] = h [ i − 1 ] [ j ] + 1 h[i][j] = h[i-1][j]+1 h[i][j]=h[i1][j]+1 ,因此高度扩展复杂度 O ( 1 ) O(1) O(1)
    • 左边界扩展:如果 h [ i ] [ j ] = 1 h[i][j] = 1 h[i][j]=1,那么 l [ i ] [ j ] = 0 l[i][j] = 0 l[i][j]=0;如果 h [ i ] [ j ] > 1 h[i][j] > 1 h[i][j]>1,那么要考虑 l [ i − 1 ] [ j ] l[i-1][j] l[i1][j]和第 i i i行内的最近左侧递减位置(tmp),因此 l [ i ] [ j ] = m a x ( l [ i − 1 ] [ j ] , t m p ) l[i][j] = max(l[i-1][j], tmp) l[i][j]=max(l[i1][j],tmp),因此左边界扩展复杂度也是 O ( 1 ) O(1) O(1)
    • 右侧边界扩展:与左侧边界扩展同理,只是应改成 r [ i ] [ j ] = m i n ( r [ i − 1 ] [ j ] , t m p ) r[i][j] = min(r[i-1][j], tmp) r[i][j]=min(r[i1][j],tmp),tmp是右侧的最近递减位置,因此右边界扩展复杂度也是 O ( 1 ) O(1) O(1)
    • 上图中, [ 3 , 12 ] ⇒ [ 4 , 12 ] [3,12] \Rightarrow [4,12] [312][412]的扩展体现了上述 状态转移关系

代码【1636MS】

/* HDU 6957 Maximal submatrix - 悬线法 */
#include<bits/stdc++.h>
using namespace std;
#define MXN 2010
int n, m, ans;
int v[MXN][MXN], h[MXN][MXN], l[MXN][MXN], r[MXN][MXN];
int main(){
	int t, tmp;
	scanf("%d", &t);
	while(t--){
		scanf("%d%d", &n, &m);
		ans = m; // 单独一行肯定满足要求
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= m; ++j) scanf("%d", v[i]+j);
		for(int i = 1; i <= m; ++i) // 第1行初值
			l[1][i] = 0, h[1][i] = 1, r[1][i]=m+1;
		// left, 从左向右递推,处理高度和左边界
		for(int i = 2; i <= n; ++i){
			tmp = 0;// 左侧最近不满足"非递减"的位置,初始值0
			for(int j = 1; j <= m; ++j){// 判断区域是否可合并分别处理
				if(v[i][j] >= v[i-1][j])// 两个区域可合并,处理左边界和高度
					l[i][j] = max(l[i-1][j], tmp), h[i][j] = h[i-1][j]+1;
				else tmp = j, l[i][j] = 0, h[i][j] = 1;// 两个区域不可合并
			}
		}
		// right,从右向左递推,处理右边界并计算面积
		for(int i = 2; i <= n; ++i){
			tmp = m+1;// 右侧最近不满足"非递减"的位置,初始值m+1
			for(int j = m; j >= 1; --j){ // 判断区域是否可合并分别处理
				if(v[i][j] >= v[i-1][j]) r[i][j] = min(r[i-1][j], tmp);				
				else tmp = j, r[i][j] = m+1;	
				ans = max(ans, (r[i][j]-l[i][j]-1)*h[i][j]);//求面积的最大
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

优先队列

分析

  • 初始状态: [ 1 , 10 ] [1,10] [110] 区间是一个满足条件的区间,如下图1
    在这里插入图片描述
  • [ 2 , 3 ] [2,3] [23] 位置上出现一个递减数,将把 [ 1 , m ] [1,m] [1m] 区间分割成 左、中、右三个区间;
  • 若已有区间是按左边界优先的方式排列,那么图2中左和中区间将不再受第2行的后续递减数的影响,而右区间可能还将受到第2行内的递减数影响;当第2行内的递减数全部处理后,将未处理的第2行以前的区间高度+1;
  • 然后添加一个高度1的 [ 1 , 10 ] [1,10] [110] 区间,表示第2行可以构成一个区间【此区间说明了分割出来的中区间无需加入】
  • 然后处理第3行,依次类推
  • 区间有序通过优先队列实现,且采用2个优先队列的滚动方式
  • 待处理的区间放一个队列(原队列),左区间放另一个队列(新队列),右区间放原队列;处理完一行后,原队列和新队列角色互换。
  • 优化:同底的保留高度最大的,同时考虑现有的区域的最大可能值,若无法达到已有的最大值则抛弃

代码【2636MS】

/* HDU 6957 Maximal submatrix - DP+优先队列+剪枝 */
#include<bits/stdc++.h>
using namespace std;
#define MXN 2010
int n, m, v[MXN], ans;
int mt[2][MXN][MXN];
void *ptr;
struct Range{
	priority_queue<Range> *q;
	int l, r, h, line;
	bool operator<(const Range x)const{
		if(l == x.l && r == x.r) return h < x.h;
		else if(l == x.l) return r > x.r;
		else return l > x.l;
	}
	Range(int line, int l, int r, int h):line(line),l(l),r(r),h(h){
		if(ptr != NULL) q = (priority_queue<Range> *)ptr;
	}
	int area(){ return (r-l+1)*h;}
	int mxArea(){ return (r-l+1)*(h+n-line);}// 可能的最大面积
	Range left(int x){
		return Range(line+1, l, r < x ? r : x-1, x <= l ? -1 : h+1);
	}
	Range right(int x){
		return Range(line, x+1, r, r > x ? 1 : -1);
	}
	void add(){
		if(h <= mt[line&1][l][r]) return;
		if(mxArea() <= ans) return;
		ans = max(ans, area());
		q[line&1].push(*this);
	}
	void split(int x){
		left(x).add(), right(x).add();
	}
};
priority_queue<Range> pq[2];
void solve(int x, int cline){
	priority_queue<Range> &tq = pq[cline&1];
	while(!tq.empty() && tq.top().l <= x){					
		Range tr = tq.top();
		tq.pop();
		mt[cline&1][tr.l][tr.r] = 0;
		tr.split(x);
	}
}
int main(){
	int t, tv;
	ptr = (void*)pq;
	scanf("%d", &t);
	while(t--){
		memset(v, 0, sizeof v);		
		memset(mt, 0, sizeof mt);
		while(!pq[0].empty()) pq[0].pop();
		while(!pq[1].empty()) pq[1].pop();		
		scanf("%d%d", &n, &m);
		ans = m, mt[0][1][m] = -1;		
		Range(0,1,m,0).add();
		for(int i = 1; i <= n; ++i){
			for(int j = 1; j <= m; ++j){
				tv = v[j];
				scanf("%d", v+j);
				if(v[j] >= tv) continue;
				solve(j, i-1);
			}
			solve(m+1, i-1);
			Range(i, 1, m, 1).add();
		}
		printf("%d\n", ans);
	}
	return 0;
}

单调栈

分析

  • 不同的矩形的生命周期是不同的,左右边界可代表其生命周期。用栈 s t a c k < i n t > stack<int> stack<int> 维护多个同底、同右边界的矩形区域的左边界,而且栈内的左边界具有单调递增的特征,此外还辅助一个维护高度的数组 H [ M X N ] H[MXN] H[MXN] 和维护有边界的变量(即内层循环变量 j j j)。什么时候诞生?什么时候消亡?
    在这里插入图片描述

  • 如图0所示,有7个同底的相邻的矩形区域(为了代码方便,在边界 0 0 0 8 8 8 位置加入了高度 0 0 0 0 0 0 的假想矩形),这9个矩形区域的高度分别是 0 、 2 、 4 、 7 、 5 、 5 、 3 、 1 、 0 0、2、4、7、5、5、3、1、0 024755310.

在这里插入图片描述

  • 图1:加入左边界位置上的假想矩形区域,表示当前新增了一个需要维护的矩形区域,左边界L = 右边界R = 高度H = 0,

  • 图2:新增了一个 L = R = 1 、 H = 2 L = R = 1、H=2 L=R=1H=2 的矩形区域,同时将原有矩形区域的 R R R 扩展到1,此时栈维护了2个矩形区域

  • 图3:新增了一个 L = R = 2 、 H = 4 L = R = 2、H=4 L=R=2H=4 的矩形区域,同时将原有矩形区域的 R R R 扩展到2,此时栈维护了3个矩形区域

  • 图4:新增了一个 L = R = 3 、 H = 7 L = R = 3、H=7 L=R=3H=7 的矩形区域,同时将原有矩形区域的 R R R 扩展到3,此时栈维护了4个矩形区域

  • 图5:新增了一个 L = 3 、 R = 4 、 H = 5 L = 3、R = 4、H=5 L=3R=4H=5 的矩形区域,原有 L = R = 3 、 H = 7 L = R = 3、H=7 L=R=3H=7 矩形区域被移除,此时栈维护了4个矩形区域,它们的右边界 R R R 扩展到4。为什么会这样?

    • 首先,因为新增的矩形区域H=5,所以原有H=7的矩形区域不能继续扩展,且不必继续维护,可移除
    • 其次,虽然 H= 7不能延续,但 H=5 的矩形可以利用H=7的矩形向左扩展,扩展出 L = 3 、 R = 4 、 H = 5 L = 3、R = 4、H=5 L=3R=4H=5 的矩形
    • 此时栈维护了4个矩形区域,除了H=0的矩形外,另三个矩形如图10所示
      在这里插入图片描述
  • 图6:由于要新增的的矩形区域 H = 5 H=5 H=5 ,与原有最大高度的矩形区域高度一致,因此只要扩展 R R R 到5即可,此时栈继续维护4个矩形区域

  • 图7:移除 H = 4 H=4 H=4 H = 5 H=5 H=5 ,新增 H = 3 H=3 H=3 的矩形区域,扩展 R R R 到6

  • 图8:移除 H = 2 H=2 H=2 H = 3 H=3 H=3 ,新增 H = 1 H=1 H=1 的矩形区域,扩展 R R R 到7

  • 图9:移除 H = 0 H = 0 H=0 的矩形区域外的所有矩形区域

代码【1794MS】

/* HDU 6957 Maximal submatrix - 单调栈 */
#include<bits/stdc++.h>
using namespace std;
#define MXN 2010
int n, m, h[MXN][MXN], H[MXN], v[MXN], ans;
stack<int> st;
void add(int r, int c, int th){
	int tc = c;
	while(H[st.top()] > th){// st非空
		tc = st.top();		
		ans = max(ans, (c-tc)*H[tc]);		
		st.pop();
	}
	if(th > 0 && H[st.top()] < th) st.push(tc), H[tc] = th;
}
int main(){
	int t, tv;
	st.push(0), H[0] = 0; // 左边界假想的高度0的矩形,且有st始终非空
	scanf("%d", &t);
	while(t--){
		memset(v, 0, sizeof v), memset(h, 0, sizeof h);
		scanf("%d%d", &n, &m);
		ans = m;
		for(int i = 1; i <= n; ++i){
			for(int j = 1; j <= m; ++j){
				tv = v[j];
				scanf("%d", v+j);
				h[i][j] = v[j] >= tv ? h[i-1][j]+1 : 1;
				add(i, j, h[i][j]);
			}
			add(i, m+1, 0); // 一行结束,右边界假想的高度0的矩形
		}
		printf("%d\n", ans);
	}
	return 0;
}

DP(暴力枚举)

分析

  • f [ i ] [ j ] f[i][j] f[i][j]:底边从 i i i j j j 的矩形的高度
  • 复杂度: O ( n ∗ n ∗ m ) O(n*n*m) O(nnm),超时

代码【TLE】

#include<bits/stdc++.h>
using namespace std;
#define MXN 2010
int n, m, v[MXN], tv, ans;
int mt[MXN][MXN];
void ext1(int l, int r){
	for(int i = l; i <= r; ++i)
		++mt[i][r], ans = max(ans, (r-i+1)*mt[i][r]);
}
void ext2(int l, int r){
	for(int i = l; i <= r; ++i)
		for(int j = r; j <= m; ++j)
			mt[i][j] = 1, ans = max(ans, j-i+1);
}
int main(){
	int t, l, r;
	scanf("%d", &t);
	while(t--){
		memset(v, 0, sizeof v);
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= m; ++j) mt[i][j]= 0;
		ans = 0;
		for(int i = 1; i <= n; ++i){
			l = 1;
			for(int j = 1; j <= m; ++j){
				scanf("%d", &tv);
				if(v[j] <= tv) ext1(l, j);
				else ext2(l, j), l = j+1;
				v[j] = tv;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于HDU4546问题,还可以使用优先队列(Priority Queue)来解决。以下是使用优先队列的解法思路: 1. 首先,将数组a进行排序,以便后续处理。 2. 创建一个优先队列(最小堆),用于存储组合之和的候选值。 3. 初始化优先队列,将初始情况(即前0个数的组合之和)加入队列。 4. 开始从1到n遍历数组a的元素,对于每个元素a[i],将当前队列中的所有候选值取出,分别加上a[i],然后再将加和的结果作为新的候选值加入队列。 5. 重复步骤4直到遍历完所有元素。 6. 当队列的大小超过k时,将队列中的最小值弹出。 7. 最后,队列中的所有候选值之和即为前k小的组合之和。 以下是使用优先队列解决HDU4546问题的代码示例: ```cpp #include <iostream> #include <vector> #include <queue> #include <functional> using namespace std; int main() { int n, k; cin >> n >> k; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } sort(a.begin(), a.end()); // 对数组a进行排序 priority_queue<long long, vector<long long>, greater<long long>> pq; // 最小堆 pq.push(0); // 初始情况,前0个数的组合之和为0 for (int i = 0; i < n; i++) { long long num = pq.top(); // 取出当前队列中的最小值 pq.pop(); for (int j = i + 1; j <= n; j++) { pq.push(num + a[i]); // 将所有加和结果作为新的候选值加入队列 num += a[i]; } if (pq.size() > k) { pq.pop(); // 当队列大小超过k时,弹出最小值 } } long long sum = 0; while (!pq.empty()) { sum += pq.top(); // 求队列中所有候选值之和 pq.pop(); } cout << sum << endl; return 0; } ``` 使用优先队列的方法可以有效地找到前k小的组合之和,时间复杂度为O(nklog(k))。希望这个解法对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jpphy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值