[caioj] 问题 E: 单调队列3 (线段树)

题目描述
【题意】
给一个N*M的数矩阵
现在求一个子矩阵 要求子矩阵中最大值与最小值的差<=C。而且子矩阵的宽度(横)不超过100(长(竖)没有限制)。 求子矩阵的最大面积。
【输入格式】
第一行两个整数 M(左右方向),N(上下方向)和 C (N,M<=500 0<=C<= 10 )
接下来 N行 每行M个数 每个数(-30000~30000)
【输出格式】
子矩阵的最大面积
【样例输入】
10 15 4
41 40 41 38 39 39 40 42 40 40
39 40 43 40 36 37 35 39 42 42
44 41 39 40 38 40 41 38 35 37
38 38 33 39 36 37 32 36 38 40
39 40 39 39 39 40 40 41 43 41
39 40 41 38 39 38 39 39 39 42
36 39 39 39 39 40 39 41 40 41
31 37 36 41 41 40 39 41 40 40
40 40 40 42 41 40 39 39 39 39
42 40 44 40 38 40 39 39 37 41
41 41 40 39 39 40 41 40 39 40
47 45 49 43 43 41 41 40 39 42
42 41 41 39 40 39 42 40 42 42
41 44 49 43 46 41 42 41 42 42
45 40 42 42 46 42 44 40 42 41
【样例输出】
35

这是一个好题啊啊啊!感觉我不能直接做出来都是好题。。
题目叫做单调队列,因为这是scy给我们单调队列的作业啦
一开始我还是很务正业的,写了一发单调队列
但是由于是暴力枚举答案+判断,判断可以类似bzoj的理想正方形
然后复杂度有点爆炸
于是就T到不知道哪里去了。。但居然有80分。。
(下面代码由于数据范围改了,所以有一点点冲突。。但问题不大)

#include<cstdio>
#include<cstring>
const int MAX=1<<30;
const int N=705;
int m,n,c;
int a[N][N];
int ans=0;
int mymax (int x,int y){return x>y?x:y;}
int mymin (int x,int y){return x<y?x:y;}
int f[N][N];//第i行以j结尾的前n个数的最大(横着)
int f1[N][N];//第i行以j结尾的前n个数的最小(横着)
int f2[N][N];//第i行以j结尾的前n个数的最大(竖着)
int f3[N][N];//第i行以j结尾的前n个数的最小(竖着)
int q[N],q1[N];
void heng (int x,int y)
{
    for (int u=1;u<=n;u++)
    {
        f[u][1]=a[u][1];q[1]=a[u][1];q1[1]=1;
        int st=1,ed=1;
        for (int i=2;i<=m;i++)
        {
            while (st<=ed&&q1[st]<=i-y) st++;
            while (st<=ed&&q[ed]<=a[u][i]) ed--;
            q[++ed]=a[u][i];q1[ed]=i;f[u][i]=q[st];
        }
    }
    for (int u=1;u<=n;u++)
    {
        f1[u][1]=a[u][1];q[1]=a[u][1];q1[1]=1;
        int st=1,ed=1;
        for (int i=2;i<=m;i++)
        {
            while (st<=ed&&q1[st]<=i-y) st++;
            while (st<=ed&&q[ed]>=a[u][i]) ed--;
            q[++ed]=a[u][i];q1[ed]=i;f1[u][i]=q[st];
        }
    }
}
int shu (int x,int y)
{
    for (int u=1;u<=m;u++)
    {
        f2[1][u]=f[1][u];q[1]=f[1][u];q1[1]=1;
        int st=1,ed=1;
        for (int i=2;i<=n;i++)
        {
            while (st<=ed&&q1[st]<=i-x) st++;
            while (st<=ed&&q[ed]<=f[i][u]) ed--;
            q[++ed]=f[i][u];q1[ed]=i;f2[i][u]=q[st];
        }
    }
    for (int u=1;u<=m;u++)
    {
        f3[1][u]=f1[1][u];q[1]=f1[1][u];q1[1]=1;
        int st=1,ed=1;
        for (int i=2;i<=n;i++)
        {
            while (st<=ed&&q1[st]<=i-x) st++;
            while (st<=ed&&q[ed]>=f1[i][u]) ed--;
            q[++ed]=f1[i][u];q1[ed]=i;f3[i][u]=q[st];
        }
    }
}
bool check (int x,int y)//问题转化为在一个n*m的图中是否存在一个x*y的矩阵使得那啥 
{
    heng(x,y);
    shu(x,y);
    for (int u=x;u<=n;u++)
        for (int i=y;i<=m;i++)
            if (f2[u][i]-f3[u][i]<=c)
                return true;
    return false;
}
int main()
{
    scanf("%d%d%d",&m,&n,&c);
    for (int u=1;u<=n;u++)
        for (int i=1;i<=m;i++)
            scanf("%d",&a[u][i]);
    for (int u=mymin(100,n);u>=1;u--)
    {
        int l=1,r=mymin(100,m);
        while (l<=r)
        {
            if (u*r<=ans) break;
            int mid=(l+r)>>1;
            if (check(u,mid)==true) {ans=mymax(ans,u*mid);l=mid+1;}
            else r=mid-1;
        }
    }
    printf("%d\n",ans);
    return 0;
}

于是就有了更好的单调队列做法,然而我不会TAT
那我还是用我较熟悉的吧——线段树
大概思路就是先枚举左右的范围,这里是 O(m100)
然后枚举下面边界,很明显,上面边界是可以二分的
于是就转化为一个矩阵是否可以
那么怎么求最大最小值呢?
我们可以弄一个可持久化线段树,于是就可以以二分的姿势来弄出来最大最小值了
这个的复杂度是 O(m100nlog(n)log(T)) T为数据范围
然而这么复杂度只有60分QAQ但后来我加了些玄学优化就80分了

#include<cstdio>
#include<cstdlib>
#include<cstring>
const int N=505;
const int M=30001*2;
int m,n,c;
struct qq
{
    int s1,s2;
    int c;
}s[N*N*50];
int root[N][N],num=0;
int a[N][N];
void change (int &now,int l,int r,int x)
{
    if (now==0) now=++num;
    if (l==r)
    {
        s[now].c=1;
        return ;
    }
    int mid=(l+r)>>1;
    if (x<=mid) change(s[now].s1,l,mid,x);
    else change(s[now].s2,mid+1,r,x);
    s[now].c=s[s[now].s1].c+s[s[now].s2].c;
}
int Merge (int x,int y)//将xy合并 
{
    if (!x||!y) return x+y;  
    int z=++num;
    s[z].c=s[x].c+s[y].c;
    s[z].s1=Merge(s[x].s1,s[y].s1);  
    s[z].s2=Merge(s[x].s2,s[y].s2); 
    return z;
}
int mymin (int x,int y){return x<y?x:y;}
int mymax (int x,int y){return x>y?x:y;}
int get_min (int rt1,int rt2,int rt3,int rt4,int l,int r)
{
//  printf("%d %d\n",l,r);
    if (l==r)   return l;
    int s1=s[rt1].s1;
    int s2=s[rt2].s1;
    int s3=s[rt3].s1;
    int s4=s[rt4].s1;
    int mid=(l+r)>>1;
    if (s[s1].c-s[s2].c-s[s3].c+s[s4].c>0) return get_min(s1,s2,s3,s4,l,mid);
    else return get_min(s[rt1].s2,s[rt2].s2,s[rt3].s2,s[rt4].s2,mid+1,r);
}
int get_max (int rt1,int rt2,int rt3,int rt4,int l,int r)
{
/*  printf("%d %d\n",l,r);
    system("pause");*/
    if (l==r)   return l;
    int s1=s[rt1].s2;
    int s2=s[rt2].s2;
    int s3=s[rt3].s2;
    int s4=s[rt4].s2;
    int mid=(l+r)>>1;
    if (s[s1].c-s[s2].c-s[s3].c+s[s4].c>0) return get_max(s1,s2,s3,s4,mid+1,r);
    else return get_max(s[rt1].s1,s[rt2].s1,s[rt3].s1,s[rt4].s1,l,mid);
}
bool check (int y1,int y2,int x1,int x2)//x1<=x2   y1<=y2
{
//  printf("%d %d %d %d\n",x1,y1,x2,y2);
    int a=get_min(root[x2][y2],root[x1-1][y2],root[x2][y1-1],root[x1-1][y1-1],1,M);
    int b=get_max(root[x2][y2],root[x1-1][y2],root[x2][y1-1],root[x1-1][y1-1],1,M);   
    //printf("%d %d\n",a,b);
    if (b-a<=c) return true;
    return false;
}
void dfs (int now,int l,int r)
{
    if (now==0) return ;
    //printf("%d %d %d\n",l,r,s[now].c);
    int mid=(l+r)>>1;
    dfs(s[now].s1,l,mid);
    dfs(s[now].s2,mid+1,r);
}
void change1 (int rt1,int rt2,int l,int r)
{
    if (rt2==0) return ;
    s[rt1].c-=s[rt2].c;
    if (l==r) return ;
    int mid=(l+r)>>1;
    change1(s[rt1].s1,s[rt2].s1,l,mid);
    change1(s[rt1].s2,s[rt2].s2,mid+1,r);
}
int main()
{
    num=0;
    scanf("%d%d%d",&m,&n,&c);
    for (int u=1;u<=n;u++)
        for (int i=1;i<=m;i++)
        {
            scanf("%d",&a[u][i]);
            a[u][i]+=30001;
        }
    for (int u=1;u<=n;u++)   {change(root[u][1],1,M,a[u][1]);root[u][1]=Merge(root[u][1],root[u-1][1]);}
//  dfs(root[10][1],1,M);
    for (int u=1;u<=m;u++)  {change(root[1][u],1,M,a[1][u]);root[1][u]=Merge(root[1][u],root[1][u-1]);}
    for (int u=2;u<=n;u++)
        for (int i=2;i<=m;i++)
        {
            change(root[u][i],1,M,a[u][i]);
            root[u][i]=Merge(root[u][i],root[u-1][i]);
            root[u][i]=Merge(root[u][i],root[u][i-1]);
            change1(root[u][i],root[u-1][i-1],1,M);
        }
    int ans=0;
  for (int u=1;u<=m;u++)
    {
        for (int j=u;j<=mymin(u+99,m);j++)
        {
            for (int k=1;k<=n;k++)
            {
                int l=1,r=k;//上端点在哪里 
                int ooo=-1;
                while (l<=r)
                {
                    int mid=(l+r)>>1;
                    if (check(u,j,mid,k)==true) {ooo=mid;r=mid-1;}
                    else l=mid+1;
                }
                if (ooo!=-1) 
                {
                    //if ((j-u+1)*(k-ooo+1)==150) printf("%d %d %d %d\n",u,j,ooo,k);
                    ans=mymax(ans,(j-u+1)*(k-ooo+1));
                }
            }
        }
    }
   //check(1,10,1,15);
    printf("%d\n",ans);
    return 0;
}

于是经过我冥思苦想。。我们发现,二分上界是没有必要的,我们可以线性扫过去,因为上面不可以,下面肯定也不可以!于是就少了一个log
然后我就想,要是可以两个线段树一起跑,肯定可以快一些,因为中途不满足就false了!
同时,我又想了一个十分强力的优化,要是宽度是递增的,那么对于同一个下界,他的上界肯定是递减的,于是就可以开一个数组来记录。。
那么这样的话理论复杂度就是 O(nmlog(T))
但是似乎常数有一点点大,于是跑得比正解单调队列的 O(nm100) 慢,自带大常数,唉。。
但还是蛮快的啦,哦,空间也有一点点大。。
然而poj有点卡空间。。
这题做了我很久啊!
同时,因为我们的数字有负数,我们要把它转正啊,要不不是很好搞

#include<cstdio>
#include<cstdlib>
#include<cstring>
const int N=505;
const int MAX=1<<30;
int M=0;
int MM=MAX;
int m,n,c;
struct qq
{
    int s1,s2;
    int c;
}s[N*N*100];
int root[N][N],num=0;
int a[N][N];
int cnt=0,cntt=0;
void change (int &now,int l,int r,int x)
{
    if (now==0) now=++num;
    if (l==r)
    {
        s[now].c++;
        return ;
    }
    int mid=(l+r)>>1;
    if (x<=mid) change(s[now].s1,l,mid,x);
    else change(s[now].s2,mid+1,r,x);
    s[now].c=s[s[now].s1].c+s[s[now].s2].c;
}
int Merge (int x,int y)//将x和y合并 
{
    if (!x||!y) return x+y;  
    int z=++num;
    s[z].c=s[x].c+s[y].c;
    s[z].s1=Merge(s[x].s1,s[y].s1);  
    s[z].s2=Merge(s[x].s2,s[y].s2); 
    return z;
}
int mymin (int x,int y){return x<y?x:y;}
int mymax (int x,int y){return x>y?x:y;}
bool check (int y1,int y2,int x1,int x2)//x1<=x2   y1<=y2
{
    int rt1=root[x2][y2],rt2=root[x1-1][y2],rt3=root[x2][y1-1],rt4=root[x1-1][y1-1];
    int Rt1=root[x2][y2],Rt2=root[x1-1][y2],Rt3=root[x2][y1-1],Rt4=root[x1-1][y1-1];
    int l=MM,r=M,L=MM,R=M;
    while (l!=r||L!=R)
    {
        if (L-r>c) return false;
        if (l!=r)
        {
             int s1=s[rt1].s1,s2=s[rt2].s1,s3=s[rt3].s1,s4=s[rt4].s1;
             int mid=(l+r)>>1;
             if (s[s1].c-s[s2].c-s[s3].c+s[s4].c>0) {rt1=s1;rt2=s2;rt3=s3;rt4=s4;r=mid;}
             else rt1=s[rt1].s2,rt2=s[rt2].s2,rt3=s[rt3].s2,rt4=s[rt4].s2,l=mid+1;
        }
        if (L!=R)
        {
             int s1=s[Rt1].s2,s2=s[Rt2].s2,s3=s[Rt3].s2,s4=s[Rt4].s2;
             int mid=(L+R)>>1; 
             if (s[s1].c-s[s2].c-s[s3].c+s[s4].c>0) {Rt1=s1;Rt2=s2;Rt3=s3;Rt4=s4;L=mid+1;}
             else Rt1=s[Rt1].s1,Rt2=s[Rt2].s1,Rt3=s[Rt3].s1,Rt4=s[Rt4].s1,R=mid;
        }
    }
    return L-l<=c;
}
void dfs (int now,int l,int r)
{
    if (now==0) return ;
    int mid=(l+r)>>1;
    dfs(s[now].s1,l,mid);
    dfs(s[now].s2,mid+1,r);
}
void change1 (int &rt1,int rt2,int l,int r)
{
    if (rt2==0) return ;
    if (rt1==0) rt1=++num;
    s[rt1].c+=s[rt2].c;
    if (l==r) return ;
    int mid=(l+r)>>1;
    change1(s[rt1].s1,s[rt2].s1,l,mid);
    change1(s[rt1].s2,s[rt2].s2,mid+1,r);
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int ok[N];
int shen;
int main()
{
   // freopen("airport9.in","r",stdin);
    num=0;s[0].s1=s[0].s2=0;
    m=read();n=read();c=read();
    for (int u=1;u<=n;u++)
        for (int i=1;i<=m;i++)
        {
            a[u][i]=read();
            a[u][i]+=30001;
            M=mymax(M,a[u][i]);
            MM=mymin(MM,a[u][i]);
        }
    for (int u=1;u<=n;u++)   {change(root[u][1],MM,M,a[u][1]);root[u][1]=Merge(root[u][1],root[u-1][1]);}
    for (int u=1;u<=m;u++)  {change(root[1][u],MM,M,a[1][u]);root[1][u]=Merge(root[1][u],root[1][u-1]);}
    for (int u=2;u<=n;u++)
    {
        shen=0;change(shen,MM,M,a[u][1]);
        for (int i=2;i<=m;i++)
        {
            change(shen,MM,M,a[u][i]);
            change1(root[u][i],shen,MM,M);
            root[u][i]=Merge(root[u][i],root[u-1][i]);
        }
    }
    int ans=0;
    for (int u=1;u<=m;u++)
    {
        for (int i=1;i<=n;i++) ok[i]=1;
        for (int j=u;j<=mymin(u+99,m);j++)
        {
            int L=(j-u+1);
            int l=1;
            for (int k=1;k<=n;k++)
            {
                l=mymax(l,ok[k]);
                for (;l<=k;l++)
                {
                    if (check(u,j,l,k)==true)
                        break;
                }
                ok[k]=l;
                if (l<=k) ans=mymax(ans,L*(k-l+1));
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

在博客的最后,大概提一下单调队列的做法吧。。是tyb大佬告诉我的

我的方法是先预处理出mx[i][j][k]和mn[i][j][k],分别表示第i行第j列的数数起k个数中的最大最小值,然后枚举列的起点j与跨度k,然后用一个单调队列维护最多能延伸多少行,时间复杂度为O(100∗n∗m)。

但tyb被卡了一个点,哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值