2019银川区域赛 K题 Largest Common Submatrix 子矩阵(单调栈,悬线法两种解法)

题目

给你两个矩阵矩阵内的数字为N*M的排列。
找出两矩阵的子矩阵能相等的最大大小。
N,M<=1000
链接

题解思路
单调栈解法

我们可以维护两个矩阵中其中一个的每个数的列可以往左扩展的距离。(这一步应该比较容易想到)
然后问题就变成了对于每一列的数,求每个数能往左右不超过他的值扩展的距离最大值。
再乘以长度就是矩形的大小。
在这里插入图片描述
运用单调栈后置处理
单挑栈存小值。
并且存上这个点能往左边扩展的最大值stk[i].l。
定义一个临时变量l,用于记录这个点能往右边扩展的最大值。
l只需加上之前出栈的点+1即可。因为这个点必然小于之前出栈的点。
大概情况是这样。
在这里插入图片描述
随后入栈的这个点肯定是比这些点都小的,所以把之前累加的 l 赋值给它。
当无法往下延展时需要特判,以及让栈空。
这里体现出的后置思想让这部分理解有点绕。
还是题目血少了。

悬线法

悬线法介绍
将a数组映射成标准数组,然后b数组的每个值根据a数组的映射值对应改变。
然后就能用悬线法来操作了。(至于为什么能,这是悬线法的性质,可以看这篇博客里面的论文
列之间差1,行之间差m。
套上悬线法的板子直接能过。

AC代码

单调栈解法

/*从你的全世界路过.*/
#include <bits/stdc++.h>
//#include <unordered_map>
//priority_queue
#define PII pair<int,int>
#define ll long long

using namespace std;

const  int  INF =  0x3f3f3f3f;
const  int  N =  200100;
int n , m ; 
int a[2010][2010] ; 
int b[2010][2010] ; 
int f[2010][2010] ; 
int ans = 0 ; 
PII mp1[2010*2010] , mp2[2010*2010] ; 
struct node 
{
    int l , h ,id ; 
}stk[2010] ; 
void work(int j )
{
    for (int i = 0 ; i <= n+1 ; i++ )
        stk[i] = {0,0,0} ;
    int top = 0 ; 
    mp2[0].first = INF , mp2[0].second = INF ; 
    f[n+1][j] = 0 ; 
    int falg = 0 ; 
    for (int i = 1 ; i <= n + 1 ; i++ )
    {
        if ( mp1[b[i][j]].first == mp1[b[i-1][j]].first + 1 && mp1[b[i][j]].second == mp1[b[i-1][j]].second )
            falg = 1 ; 
        else
            falg = 0 ; 
        int l = 0 , len = 0 ; 
        while ( top && (stk[top].h > f[i][j] || !falg ) )
        {
            int s = (stk[top].l + 1 + l)*(stk[top].h);
            l += stk[top].l + 1 ; 
            ans = max(s,ans) ; 
            top--;
        }
        stk[++top].h = f[i][j]  ; 
        if ( falg )
            stk[top].l = l ; 
        else
            stk[top].l = 0 ; 
    }
}
void solve()
{
    cin >> n >> m ;
    for (int i = 1 ; i <= n ; i++ )
        for (int j = 1 ; j <= m ; j++ )
            cin >> a[i][j] , mp1[a[i][j]] = {i,j} ; 
    for (int i = 1 ; i <= n ; i++ )
        for (int j = 1 ; j <= m ; j++ )
            cin >> b[i][j] , mp2[b[i][j]] = {i,j} ;
    for (int i = 1 ; i <= n ; i++ )
        for (int j = 1 ; j <= m ; j++ )
        {
            if ( mp1[b[i][j]].first == mp1[b[i][j-1]].first && mp1[b[i][j]].second == mp1[b[i][j-1]].second + 1 )
            {
                f[i][j] = f[i][j-1] + 1 ; 
            }else
                f[i][j] = 1 ; 
        }
    for (int j = 1 ; j <= m ; j++ )
        work(j) ; 
    cout << ans << "\n" ; 
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
        solve() ;
    return 0 ;
}

悬线法

/*从你的全世界路过.*/
#include <bits/stdc++.h>
//#include <unordered_map>
//priority_queue
#define PII pair<int,int>
#define ll long long

using namespace std;

const  int  INF =  0x3f3f3f3f;
const  int  N =  200100;
int a[1010][1010] ; 
int vis[1010*1010] ; 
int l[2010][2010];
int r[2010][2010]; 
int up[2010][2010]; 
void solve()
{
	int n  , m ;
	cin >> n >> m ;
	for (int i = 1 ; i <= n*m ; i++ )
	{
		int t1 ;
		cin >> t1 ;
		vis[t1] = i;
	}
	for (int i = 1 ; i <= n ; i++ )
		for (int j = 1 ; j <= m ; j++ )
		{
			int t1 ;
			cin >> t1 ;
			a[i][j] = vis[t1] ; 
			l[i][j] = r[i][j] = j ; 
			up[i][j] = 1 ; 
		}
	for (int i = 1 ; i <= n ; i++ )
		for (int j = 2 ; j <= m ; j++ )
			if ( a[i][j] == a[i][j-1] + 1 )
				l[i][j] = l[i][j-1] ; 
	for (int i = 1 ; i <= n ; i++ )
		for (int j = m-1 ; j >= 1 ; j-- )
			if ( a[i][j] + 1 == a[i][j+1] )
				r[i][j] = r[i][j+1] ; 
	int ans = 0 ; 
	for (int i = 1 ; i <= n ; i++ )
		for (int j = 1 ; j <= m ; j++ )
		{
			if ( i > 1 && a[i-1][j] + m  == a[i][j] ) 
			{
				up[i][j] = up[i-1][j] + 1 ; 
				r[i][j] = min(r[i-1][j],r[i][j]);
				l[i][j] = max(l[i-1][j],l[i][j]) ; 
			}
			ans = max(ans,up[i][j]*(r[i][j]-l[i][j]+1)) ; 
		}
	cout << ans << "\n" ; 
}
int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
		solve() ;
	return 0 ;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值