「2019牛客+杭电多校」 单调栈和单调队列题目汇总【持续更新】

2019牛客多校第二场H

在这里插入图片描述

题意

  • 就是给你一个矩阵只含有0或者1,让你找一个第二大的内部元素全部为1的子矩形

题解

  • 考虑枚举所有的最大矩形,删去一行或者一列构成当前矩形的第二大,记录所有这些信息,排序就行了,注意同一个矩形不要算多次,所以我这里直接重载矩形结构体的比较函数

代码

#include<bits/stdc++.h>

using namespace std;
const int maxn=1005;

struct polygon{
    int area,lx,ly,rx,ry;
    polygon(int a=0,int b=0,int c=0,int d=0,int e=0) {
        area=a;lx=b;ly=c;rx=d;ry=e;
    }
    friend bool operator<(const polygon& p1,const polygon& p2) {
        if(p1.area!=p2.area) return p1.area<p2.area;
        if(p1.lx!=p2.lx) return p1.lx<p2.lx;
        if(p1.ly!=p2.ly) return p1.ly<p2.ly;
        if(p1.rx!=p2.rx) return p1.rx<p2.rx;
        return p1.ry<p2.ry;

    }
    friend bool operator==(const polygon &p1,const polygon &p2) {
        return p1.area==p2.area&&p1.lx==p2.lx&&p1.ly==p2.ly&&p1.rx==p2.rx&&p1.ry==p2.ry;
    }
}p[3*maxn*maxn];
int cnt=0;
int n,m,dp[maxn][maxn];
char s[maxn][maxn];

int a[maxn],sta[maxn],tot,minl[maxn],minr[maxn];

void solve_min_l(int k){  //左边第一个比当前位严格小的数位置
    tot=0;
    for(int i=m;i>=1;i--) {
        while(tot&&dp[k][i]<dp[k][sta[tot]]) {minl[sta[tot]]=i;tot--;}
        sta[++tot]=i;
    }
    while(tot) {minl[sta[tot]]=0;tot--;}
}

void solve_min_r(int k){  //右边第一个比当前位严格小的数位置
    tot=0;
    for(int i=1;i<=m;i++) {
        while(tot&&dp[k][i]<dp[k][sta[tot]]) {minr[sta[tot]]=i;tot--;}
        sta[++tot]=i;
    }
    while(tot) {minr[sta[tot]]=m+1;tot--;}
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    for(int i=n;i>=1;i--) for(int j=1;j<=m;j++) dp[i][j]=(s[i][j]=='1'?dp[i+1][j]+1:0);
    for(int i=1;i<=n;i++) {
        solve_min_l(i);solve_min_r(i);
        for(int j=1;j<=m;j++) if(dp[i][j]){
            p[++cnt]=polygon(dp[i][j]*(minr[j]-minl[j]-1),i,minl[j]+1,i+dp[i][j]-1,minr[j]-1);
            if(minr[j]-minl[j]-2>0) p[++cnt]=polygon(dp[i][j]*(minr[j]-minl[j]-2),i,minl[j]+1,i+dp[i][j]-1,minr[j]-2);
            if(dp[i][j]>1) p[++cnt]=polygon((dp[i][j]-1)*(minr[j]-minl[j]-1),i,minl[j]+1,i+dp[i][j]-2,minr[j]-1);
        }
    }

    sort(p+1,p+cnt+1);
    if(!cnt) return printf("0\n"),0; 
    int maxx=0;int memor=cnt-1;
    while(memor&&p[memor]==p[cnt]) memor--;
    if(memor) maxx=p[memor].area;

    printf("%d\n",maxx);
}

2019牛客多校第三场F

在这里插入图片描述

题意

  • 就是给你一个 n × n n\times n n×n的矩阵,让你寻找一个最大的子矩阵使得这个矩阵的最大值与最小值相减小于等于 m m m

题解

  • 考虑枚举矩形的上下边界,然后考虑能不能 O ( n ) O(n) O(n)处理出最大的矩形,可以用两个单调队列分别维护区间最大最小值,当然最大值对应一个递减队列,最小值对应一个递增队列,每次插入当前这一列的最大值,最小值后,首先让维护好两个队列的单调性,然后如果递减队列的头元素减去递增队列的头元素大于 m m m,显然需要 p o p pop pop头元素,那么 p o p pop pop哪一个队列呢?可以比较一下两个队列的首元素位置大小关系,因为当前差值太大,你可以将最大值变小【 p o p pop pop递减队列】也可以将最小值变大【 p o p pop pop递增队列】,从贪心的角度,你应该选择头元素位置小的那个队列 p o p pop pop,那么怎么知道矩形的当前可行宽度呢?可以维护一个指针 p o s pos pos,表示当前可行的最左边界,每次 p o p pop pop两个队列中的任意一个队列时更新可行左边界

代码

#include<bits/stdc++.h>

using namespace std;
const int maxn=505;
#define inf 0x3f3f3f3f

namespace IO{ 
    #define BUF_SIZE 100000 
    #define OUT_SIZE 100000 

    bool IOerror=0; 
    inline char nc(){ 
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE; 
        if (p1==pend){ 
            p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin); 
            if (pend==p1){IOerror=1;return -1;} 
        } 
        return *p1++; 
    } 
    inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';} 

    template<typename T>
    inline bool read(T &x){ 
        bool sign=0; char ch=nc(); x=0; 
        for (;blank(ch);ch=nc()); 
        if (IOerror) return false; 
        if (ch=='-')sign=1,ch=nc(); 
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; 
        if (sign)x=-x; return true;
    } 
};
using namespace IO;

int a[maxn][maxn],maxx[maxn],minn[maxn],que[2][maxn],head[2],tail[2],n,m;

void init_que()
{
	head[0]=head[1]=1;
	tail[0]=tail[1]=0;
}

int main()
{
	int t;read(t);
	while(t--) {
		read(n);read(m);int ans=0;
		for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) read(a[i][j]);
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=n;j++) minn[j]=inf,maxx[j]=-inf;
			for(int j=i;j<=n;j++) {
				init_que();
				for(int k=1,pos=0;k<=n;k++){
					minn[k]=min(minn[k],a[j][k]),maxx[k]=max(maxx[k],a[j][k]);
					while(head[0]<=tail[0]&&maxx[k]>=maxx[que[0][tail[0]]]) tail[0]--;//递减队列,维护最大值
					while(head[1]<=tail[1]&&minn[k]<=minn[que[1][tail[1]]]) tail[1]--;//递增队列,维护最小值
					que[0][++tail[0]]=k;que[1][++tail[1]]=k;
					while(head[0]<=tail[0]&&head[1]<=tail[1]&&maxx[que[0][head[0]]]-minn[que[1][head[1]]]>m) {
						if(que[0][head[0]]>que[1][head[1]]){
                            if(head[1]<tail[1]) pos=que[1][head[1]++];
                            else head[1]++,pos=k;
                        }else {
                            if(head[0]<tail[0]) pos=que[0][head[0]++];
                            else head[0]++,pos=k;
                        }
					}
					ans=max(ans,(j-i+1)*(k-pos)); 
				}
			}
		}
		printf("%d\n",ans);
	}
}

2019牛客多校第四场C

在这里插入图片描述

题意

  • 就是给两个数组,求题面中的那个式子

题解

  • 由于涉及区间最小数,考虑枚举每一个数作为区间最小值时的最大值,显然这个数作为某个区间的最小值的话这个区间是有限制的,所以考虑单调栈求出每一个点对应的合法区间,然后线段树维护【这题不能 s t st st表,空间会爆】数组 b b b的前缀和的区间最大值以及最小值,如果枚举的最小值为负,由于要乘起来最大,所以可以把当前节点右边合法的最小值减去左边最大值,如果为正则相反
  • 这题还可以建数组 a a a的笛卡尔树,每个节点保存这个节点的子树的所有b的前缀和 s u m sum sum最大,最小值,然后做一次dfs,讨论根节点 a a a值正负,如果为正,将右子树和根节点的 s u m sum sum最大值减左子树最小值再乘以根节点 a a a值,如果为负,反过来就好了

单调栈+线段树代码(O(n log n))

#include<bits/stdc++.h>

using namespace std;
const int maxn=3e6+10;


namespace IO{ 
    #define BUF_SIZE 100000 
    #define OUT_SIZE 100000 

    bool IOerror=0; 
    inline char nc(){ 
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE; 
        if (p1==pend){ 
            p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin); 
            if (pend==p1){IOerror=1;return -1;} 
        } 
        return *p1++; 
    } 
    inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';} 
    template<typename T>
    inline bool read(T &x){ 
        bool sign=0; char ch=nc(); x=0; 
        for (;blank(ch);ch=nc()); 
        if (IOerror) return false; 
        if (ch=='-')sign=1,ch=nc(); 
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; 
        if (sign)x=-x; return true;
    } 
};
using namespace IO;


int n,a[maxn],b[maxn],sta[maxn][2],tot,minl[maxn],minr[maxn];
long long sum[maxn];

void solve_min_l(){  //左边第一个比当前位严格小的数位置
    tot=0;
    for(int i=n;i>=1;i--) {
        while(tot&&a[i]<sta[tot][0]) {minl[sta[tot][1]]=i;tot--;}
        sta[++tot][0]=a[i];sta[tot][1]=i;
    }
    while(tot) {minl[sta[tot][1]]=0;tot--;}
}

void solve_min_r(){  //右边第一个比当前位严格小的数位置
    tot=0;
    for(int i=1;i<=n;i++) {
        while(tot&&a[i]<sta[tot][0]) {minr[sta[tot][1]]=i;tot--;}
        sta[++tot][0]=a[i];sta[tot][1]=i;
    }
    while(tot) {minr[sta[tot][1]]=n+1;tot--;}
}

long long mark[maxn<<2],minn[maxn<<2],maxx[maxn<<2];
void pushup(int id)
{
    minn[id]=min(minn[id<<1],minn[id<<1|1]);
    maxx[id]=max(maxx[id<<1],maxx[id<<1|1]);
}

void down(int id)
{
    mark[id<<1]+=mark[id];mark[id<<1|1]+=mark[id];
    minn[id<<1]+=mark[id];minn[id<<1|1]+=mark[id];
    maxx[id<<1]+=mark[id];maxx[id<<1|1]+=mark[id];
    mark[id]=0;
}

void build(int id,int l,int r)
{
    mark[id]=minn[id]=maxx[id]=0;
    if(l==r) {minn[id]=maxx[id]=sum[l];return;}
    int mid=(l+r)>>1;
    build(id<<1,l,mid);build(id<<1|1,mid+1,r);
    pushup(id);
}

void update(int id,int L,int R,int l,int r,long long add)
{
    if(l<=L&&R<=r) {
        mark[id]+=add;minn[id]+=add;maxx[id]+=add;
        return;
    }
    if(mark[id]!=0) down(id);int mid=(L+R)>>1;
    if(l<=mid) update(id<<1,L,mid,l,r,add);
    if(r>mid) update(id<<1|1,mid+1,R,l,r,add);
    pushup(id);
}

long long query_max(int id,int L,int R,int l,int r)
{
    if(l<=L&&R<=r) return maxx[id];long long res=-0x3f3f3f3f3f3f3f3f;
    if(mark[id]!=0) down(id);int mid=(L+R)>>1;
    if(l<=mid) res=max(res,query_max(id<<1,L,mid,l,r));
    if(r>mid) res=max(res,query_max(id<<1|1,mid+1,R,l,r));
    return res;
}

long long query_min(int id,int L,int R,int l,int r)
{
    if(l<=L&&R<=r) return minn[id];long long res=0x3f3f3f3f3f3f3f3f;
    if(mark[id]!=0) down(id);int mid=(L+R)>>1;
    if(l<=mid) res=min(res,query_min(id<<1,L,mid,l,r));
    if(r>mid) res=min(res,query_min(id<<1|1,mid+1,R,l,r));
    return res;
}

int main()
{
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=n;i++) read(b[i]),sum[i]=sum[i-1]+b[i];
    solve_min_l();solve_min_r();build(1,0,n);

    long long ans=-0x3f3f3f3f3f3f3f3f;
    for(int i=1;i<=n;i++) {
        if(a[i]>0) ans=max(ans,a[i]*(query_max(1,0,n,i,minr[i]-1)-query_min(1,0,n,minl[i],i-1)));
        else ans=max(ans,a[i]*(query_min(1,0,n,i,minr[i]-1)-query_max(1,0,n,minl[i],i-1)));
    }
    printf("%lld\n",ans);
}

笛卡尔树代码(O(n))

#include<bits/stdc++.h>

using namespace std;
const int maxn=3e6+10;
#define inf 0x3f3f3f3f3f3f3f3f


int n,a[maxn],b[maxn];long long sum[maxn];

namespace IO{ 
    #define BUF_SIZE 100000 
    #define OUT_SIZE 100000 

    bool IOerror=0; 
    inline char nc(){ 
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE; 
        if (p1==pend){ 
            p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin); 
            if (pend==p1){IOerror=1;return -1;} 
        } 
        return *p1++; 
    } 
    inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';} 
    template<typename T>
    inline bool read(T &x){ 
        bool sign=0; char ch=nc(); x=0; 
        for (;blank(ch);ch=nc()); 
        if (IOerror) return false; 
        if (ch=='-')sign=1,ch=nc(); 
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; 
        if (sign)x=-x; return true;
    } 
};
using namespace IO;

struct dical_tree{  //小根堆笛卡尔树,值val满足堆的性质,下标key满足二叉树的性质

    int tot,sta[maxn],root; //sta维护单调值对应的在tree中的下标,tot表示栈顶的下标,root为树根
    struct node{             //sta维护的是最靠右的一条链
        int val;
        int fa,ls,rs;
        long long maxx,minn;
        node(int v=0,long long a=-inf,long long b=inf,int f=0,int l=0,int r=0) {
            val=v;maxx=a,minn=b,fa=f;ls=l;rs=r;
        }
    }tree[maxn];

    void build(int a[],int n) {
        tot=root=1;
        sta[1]=1;tree[1]=node(a[1],sum[1],sum[1],0,0,0);
        for(int i=2;i<=n;i++) {
            while(tot&&a[i]<tree[sta[tot]].val)  tot--;//大根堆改为>
            if(tot) {  //此时需要将新建节点替换tot的右儿子,并把之前的右儿子接到新建节点的左儿子身上,以保证下标构成二叉树
                tree[i]=node(a[i],sum[i],sum[i],sta[tot],tree[sta[tot]].rs,0);
                tree[tree[sta[tot]].rs].fa=i;
                tree[sta[tot]].rs=i;                        
            }else {
                tree[i]=node(a[i],sum[i],sum[i],0,root,0);
                tree[root].fa=i;
                root=i;
            }
            sta[++tot]=i;
        }
    }

    void pushup(int cur) {
        tree[cur].maxx=max(tree[cur].maxx,tree[tree[cur].ls].maxx);
        tree[cur].minn=min(tree[cur].minn,tree[tree[cur].ls].minn);
        tree[cur].maxx=max(tree[cur].maxx,tree[tree[cur].rs].maxx);
        tree[cur].minn=min(tree[cur].minn,tree[tree[cur].rs].minn);        
    }
    long long solve(int cur) {
        long long res=-inf;
        if(tree[cur].ls) res=max(res,solve(tree[cur].ls));
        if(tree[cur].rs) res=max(res,solve(tree[cur].rs));

        if(tree[cur].val>0) {
            long long lmin=inf,rmax=-inf;
            if(tree[cur].ls) lmin=tree[tree[cur].ls].minn;
            else lmin=sum[cur-1];

            rmax=max(sum[cur],tree[tree[cur].rs].maxx);
            res=max(res,tree[cur].val*(rmax-lmin));
        }
        else{
            long long lmax=-inf,rmin=inf;
            if(tree[cur].ls) lmax=tree[tree[cur].ls].maxx;
            else lmax=sum[cur-1];
            rmin=min(sum[cur],tree[tree[cur].rs].minn);
            res=max(res,tree[cur].val*(rmin-lmax));
        } 
        pushup(cur);
        return res;
    }
}dical;

int main()
{
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=n;i++) read(b[i]),sum[i]=sum[i-1]+b[i];
    dical.build(a,n);
    printf("%lld\n",dical.solve(dical.root));
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值