2019牛客暑期多校训练营(第二场)H和第八场的A单调栈

题意:给你一个01矩阵,求其中第二大的子矩阵是多少

开始想用DP 但维护的只是以每个点为矩阵的右下点对应的最大矩阵,最后通过率才92%。。
代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e3+3;
int map[maxn][maxn]={0};
struct node{
    int lx,ly,rx,ry;
    int size;
    node(int _lx,int _ly,int _rx,int _ry):lx(_lx),ly(_ly),rx(_rx),ry(_ry)
    {
        size=(rx-lx+1)*(ry-ly+1);
     }
     node()
     {
      }
    bool operator<(const node & tmp)const{
    return size<tmp.size;}
};
node dp[maxn][maxn];
int mp_col[maxn][maxn]={0};
int mp_row[maxn][maxn]={0};
int main()
{
    int n,m;
    cin>>n>>m;int maxsize=0;
    int second_maxsize=0;
    for(int j=1;j<=n;j++)
    {
        for(int i=1;i<=m;i++)
        {
            scanf("%1d",&map[j][i]);
        }
    }
    for(int j=1;j<=n;j++)
    {
        for(int i=1;i<=m;i++)
        {
            if(map[j][i]==1)
                mp_row[j][i]=mp_row[j][i-1]+1;
        }
    }
    for(int j=1;j<=m;j++)
    {
        for(int i=1;i<=n;i++)
        {
            if(map[i][j]==1)
                mp_col[i][j]=mp_col[i-1][j]+1;
        }
    }
    node tmp1,tmp2;
    for(int j=1;j<=n;j++)
    {
        for(int i=1;i<=m;i++)
        {
            if(map[j][i]==1)
            {
                tmp1=node(i,j,i,j);
                tmp2=node(i,j,i,j);
                if(map[j-1][i]!=0)//这里是核心的状态转移方程
                    if(mp_row[j][i]>=dp[j-1][i].rx-dp[j-1][i].lx+1)
                        tmp1=node(dp[j-1][i].lx,dp[j-1][i].ly,i,j);
                    else
                        tmp1=node(i+1-mp_row[j][i],dp[j-1][i].ly,i,j);
                else
                    tmp1=node(i+1-mp_row[j][i],j,i,j);
                if(map[j][i-1]!=0)
                    if(mp_col[j][i]>=dp[j][i-1].ry-dp[j][i-1].ly+1)
                        tmp2=node(dp[j][i-1].lx,dp[j][i-1].ly,i,j);
                    else
                        tmp2=node(dp[j][i-1].lx,j+1-mp_col[j][i],i,j);
                else
                    tmp2=node(i,j+1-mp_col[j][i],i,j);
 
                dp[j][i]=max(tmp1,tmp2);
                maxsize=max(dp[j][i].size,maxsize);
            }
        }
    }
    bool flag=0;int cnt=0;
    for(int j=1;j<=n;j++)
    {
        for(int i=1;i<=m;i++)
        {
            if(dp[j][i].size==maxsize)
            {
                cnt++;
            }
            else
            {
                second_maxsize=max(second_maxsize,dp[j][i].size);
            }
        }
    }
    if(cnt>1)
        second_maxsize=maxsize;
    cout<<second_maxsize<<endl;
    return 0;
}

但是这个其实求最大矩阵有用,但是中间会舍弃很多不是最大的情况,所以求的答案不对
最后实在没办法用了单调栈

单调栈的核心优势,其在入栈出栈的过程,能线性复杂度的找到数列中每个元素之后或之前第一个比它大或小的元素。因此在这种统计矩阵等问题有很大应用优势。

细节:更新答案的时候,不光计算x*y,还有(x-1)*y和(y-1)*x的情况。因为加入题目问的是最大的矩阵,单调栈统计的只是以每行为底所有的最大的矩阵,但是万一最后统计的只有一个矩阵或者其他类似情况,第二大的矩阵很可能是其包含的子矩阵,而这个子矩阵应该是面积刚好比母矩阵小一点点,故其面积只可能是(x-1)*y和(y-1)*x。
AC代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn= 1e3+100;
int s[maxn][maxn];
int st[maxn];
int f_max=0,s_max=0;
void update(int tmp)
{
	if(tmp>=f_max)//我觉得这里还是得>=   不过有个题解是>也过了。。。
	{
		s_max=f_max;
		f_max=tmp;
	}
	else
	{
		s_max=max(s_max,tmp);
	}
	return;
 } 
void calcu(int x,int y)
{
	update(x*y);
	update(x*(y-1));
	update((x-1)*y);
	return;
}
int main()
{
	int n,m;
	cin>>n>>m;int tmp;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%1d",&tmp);
			if(tmp==1)
			{
				s[i][j]=s[i-1][j]+1; //算每个点对应的向上有多少1
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		int top=0;
		s[i][m+1]=-1;
		st[top]=0;
		s[i][0]=-2;
		for(int j=1;j<=m+1;j++)
		{
			while(s[i][st[top]]>s[i][j]) //核心单调栈
			{
				int index=st[top];
				top--;
				calcu(j-st[top]-1,s[i][index]);
			}
			st[++top]=j;
		}
	}
	cout<<s_max<<endl;
}

第八场H题:
题意:
给一个01矩阵,求其中极大子矩阵的数量,极大子矩阵即其不会被其他的子矩阵完全包含。
参考blog:
https://blog.csdn.net/qq_43813163/article/details/99111888
单调栈的应用优势即在这种题目中统计到每行中以每个格子的向上高度为高的最大矩阵。我重新思考了,感觉上面的代码有问题,但就是过了,又找了一篇这个才对,个人认为https://blog.csdn.net/lgz0921/article/details/96700527
代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn= 3e3+100;
int s[maxn][maxn];
int l[maxn][maxn];//每个点左侧(包括自己)1的个数  
int f_max=0,s_max=0; 
int ans=0;
stack< pair<int,int> > st;
void calcu(int x,int y1,int y2)
{
	if(y2-y1+1<=l[x+1][y2])
		return;
	ans++;
//	cout<<x<<" "<<y1<<" "<<y2<<endl;
}
int main() { 
	int n,m;
	cin>>n>>m;int tmp;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%1d",&tmp);
			if(tmp==1)
			{
				s[i][j]=s[i-1][j]+1; //算每个点对应的向上有多少1
				l[i][j]=l[i][j-1]+1; 
			}
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		while(!st.empty())	st.pop();
		
		for(int j=1;j<=m+1;j++)
		{
			int pos=j;
			while((!st.empty())&&st.top().first>s[i][j]) //核心单调栈
			{
				pos=st.top().second;
				calcu(i,st.top().second,j-1);
				st.pop();
			}
			if(s[i][j]&&(st.empty()||st.top().first<s[i][j]))//注意 不要把两个相邻的且同样高度的加进栈里,否者会重复计算
				st.push({s[i][j],pos});
		}
	}
	cout<<ans<<endl;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值