刷题deque优化dp理想的正方形

Posted xwww666666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题deque优化dp理想的正方形相关的知识,希望对你有一定的参考价值。

有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

(1)矩阵中的所有数都不超过1,000,000,000

(2)20%的数据2<=a,b<=100,n<=a,n<=b,n<=10

(3)100%的数据2<=a,b<=1000,n<=a,n<=b,n<=100

 

首先想出的是st表形式,然后用了3维表达,加上了快读卡常:

技术图片
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
int a,b,n;
const int N=1003,M=103;
int mn[N][N][M],mx[N][N][M];

inline int read()
{
    int x=0;char c=getchar();
    while(c<0 || c>9) c=getchar();
    while(c>=0&&c<=9) x=(x<<3)+(x<<1)+c-0,c=getchar();
    return x;
} 

int main()
{
    a=read(),b=read(),n=read();
    for(int i=1;i<=a;i++)
        for(int j=1;j<=b;j++)
            mx[i][j][1]=mn[i][j][1]=read();
    
    for(int i=2;i<=n;i++)
        for(int sx=a-i+1;sx;sx--)
            for(int sy=b-i+1;sy;sy--)
            {
                mn[sx][sy][i]=min( min(mn[sx][sy][i-1],mn[sx+1][sy+1][i-1]) ,min(mn[sx][sy+1][i-1],mn[sx+1][sy][i-1]) );
                mx[sx][sy][i]=max( max(mx[sx][sy][i-1],mx[sx+1][sy+1][i-1]) ,max(mx[sx][sy+1][i-1],mx[sx+1][sy][i-1]) );
            }
            
    int ans=1<<30;
    for(int sx=a-n+1;sx;sx--)
        for(int sy=b-n+1;sy;sy--)
            ans=min(ans,mx[sx][sy][n]-mn[sx][sy][n]);
    
    printf("%d
",ans);
    return 0;
}
View Code

然后结果:20->20

卡常无效,估计是原始代码太慢了

 

然后按着题解减了一维......

20->60,神奇!

所以减去一维有可能大程度加快速度

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
int a,b,n;
const int N=1003,M=103;
int mn[N][N],mx[N][N];

inline int read()
{
    int x=0;char c=getchar();
    while(c<0 || c>9) c=getchar();
    while(c>=0&&c<=9) x=(x<<3)+(x<<1)+c-0,c=getchar();
    return x;
} 

int main()
{
    a=read(),b=read(),n=read();
    for(int i=1;i<=a;i++)
        for(int j=1;j<=b;j++)
            mx[i][j]=mn[i][j]=read();
    
    for(int i=2;i<=n;i++)
    {
        int r1=a-i+1;
        for(int sx=1;sx<=r1;sx++)
        {
            int r2=b-i+1;
            for(int sy=1;sy<=r2;sy++)
            {
                mn[sx][sy]=min( min(mn[sx][sy],mn[sx+1][sy+1]) ,min(mn[sx][sy+1],mn[sx+1][sy]) );
                mx[sx][sy]=max( max(mx[sx][sy],mx[sx+1][sy+1]) ,max(mx[sx][sy+1],mx[sx+1][sy]) );
            }
        }
    }
            
    int ans=1<<30;
    for(int sx=a-n+1;sx;sx--)
        for(int sy=b-n+1;sy;sy--)
            ans=min(ans,mx[sx][sy]-mn[sx][sy]);
    
    printf("%d
",ans);
    return 0;
}

然后一个快读,60->70

最后一个O2,过了,

然后去测别的评测机速度,不开O2全过......

所以减维技巧要学好

 

好了接下来是正经优化,

(1)RMQ

将O(n)的维护时间变成O(logn)

注意:log2和log都是关键词,不能做变量名

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
int a,b,n;
const int N=1003,M=103;
int lg[N],d[8];
int mn[N][N][8],mx[N][N][8];

inline int read()
{
    int x=0;char c=getchar();
    while(c<0 || c>9) c=getchar();
    while(c>=0&&c<=9) x=(x<<3)+(x<<1)+c-0,c=getchar();
    return x;
}

int main()
{
    a=read(),b=read(),n=read();
    for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
    d[0]=1; for(int i=1;i<=lg[n];i++) d[i]=d[i-1]<<1;
    for(int i=1;i<=a;i++)
        for(int j=1;j<=b;j++)
            mx[i][j][0]=mn[i][j][0]=read();
    
    int ans=1<<30,t1=lg[n],t2=d[t1];
    for(int i=1;i<=t1;i++)
    {
        int r1=a-d[i]+1;
        for(int sx=1;sx<=r1;sx++)
        {
            int r2=b-d[i]+1;
            for(int sy=1;sy<=r2;sy++)
            {
                mn[sx][sy][i]=min( min(mn[sx][sy][i-1],mn[sx+d[i-1]][sy+d[i-1]][i-1]) ,min(mn[sx][sy+d[i-1]][i-1],mn[sx+d[i-1]][sy][i-1]) );
                mx[sx][sy][i]=max( max(mx[sx][sy][i-1],mx[sx+d[i-1]][sy+d[i-1]][i-1]) ,max(mx[sx][sy+d[i-1]][i-1],mx[sx+d[i-1]][sy][i-1]) );
            }
        }
    } 

    for(int sx=a-n+1,ex=a+1;sx>0;sx--,ex--)
        for(int sy=b-n+1,ey=b+1;sy>0;sy--,ey--)
        {
            int a1=max( max(mx[sx][sy][t1],mx[sx][ey-t2][t1] ),max(mx[ex-t2][sy][t1],mx[ex-t2][ey-t2][t1]) );
            int a2=min( min(mn[sx][sy][t1],mn[sx][ey-t2][t1] ),min(mn[ex-t2][sy][t1],mn[ex-t2][ey-t2][t1]) );
            ans=min(ans,a1-a2);
        }
    
    printf("%d
",ans);
    return 0;
}

 

 

(2)deque

当做二位的线性单调dp来做

复制自luogu:

我的思路是...用单调队列分别维护行与列。

具体实现方法:是先用单调队列对每一行的值维护,并将a[][]每个区间的最大值,最小值分别存在X[][]和x[][]中。

那么X[][]与x[][]所存储的分别是1×n的长方形内的最大值,最小值。X[i][j]存储第i行第j~j+n-1列的长方形中的最大值。同理,x[i][j]存储第i行第j~j+n-1列的长方形中的最小值。

这时再对这两个数组的每一列上的值进行维护,将X[][]中每个区间的的最大值用Y[][]维护,将x[][]中的每个区间的最小值用y[][]维护。那么Y[i][j]存储X[][]中第i~i+n-1行第j列的长方形的最大值。同理y[i][j]存储x[][]中第i~i+n-1行第j列的长方形的最小值。

故Y[i][j]存储的实为以a[i~i+n-1][j~j+n-1]中的最大,即以i,j为左上角,边长为n的正方形中的最大值。同理,y[i][j]存储的即以i,j为左上角,边长为n的正方形中的最小值。

技术图片
#include<cstdio>
#include<cstdlib>
#include<queue>
#include<algorithm>
using namespace std;
int a,b,n;
const int N=1003,M=103;
int d[N][N];
int mx[2][N][N],mn[2][N][N];//0: (i,j)表示的是max(1,j-n+1)到j的 的最值,所以有效部分从n开始 

deque <int > q1,q2; 

int main()
{
    scanf("%d%d%d",&a,&b,&n);
    for(int i=1;i<=a;i++)
    {
        q1.clear() ,q2.clear() ;
        for(int j=1;j<=b;j++)
        {
            scanf("%d",&d[i][j]);
            
            if(!q1.empty() && q1.front() <= j-n) q1.pop_front() ;
            while(!q1.empty() && d[i][q1.back() ]<=d[i][j] ) q1.pop_back() ;
            q1.push_back(j);
            mx[0][i][j]=d[i][q1.front() ]; 
            
            if(!q2.empty() && q2.front() <= j-n) q2.pop_front() ;
            while(!q2.empty() && d[i][q2.back() ]>=d[i][j] ) q2.pop_back() ;
            q2.push_back(j);
            mn[0][i][j]=d[i][q2.front() ]; 
        }
    }
    
    for(int j=1;j<=b;j++)
    {
        q1.clear() ,q2.clear() ;
        for(int i=1;i<=a;i++)
        {
            if(!q1.empty() && q1.front() <= i-n) q1.pop_front() ;
            while(!q1.empty() && mx[0][q1.back() ][j]<=mx[0][i][j] ) q1.pop_back() ;
            q1.push_back(i);
            mx[1][i][j]=mx[0][q1.front() ][j]; 
            
            if(!q2.empty() && q2.front() <= i-n) q2.pop_front() ;
            while(!q2.empty() && mn[0][q2.back() ][j]>=mn[0][i][j] ) q2.pop_back() ;
            q2.push_back(i);
            mn[1][i][j]=mn[0][q2.front() ][j]; 
        }
    }    
    
    int ans=mx[1][n][n]-mn[1][n][n];
    for(int i=n;i<=a;i++)
        for(int j=n;j<=b;j++)
            ans=min(ans,mx[1][i][j]-mn[1][i][j]);
    printf("%d
",ans);
    return 0;
} 
View Code

 

以上是关于刷题deque优化dp理想的正方形的主要内容,如果未能解决你的问题,请参考以下文章

LintCode刷题——最大正方形

AcWing 1091. 理想的正方形

luogu 2216 理想的正方形 单调队列(其实没有DP)

$bzoj1047-HAOI2007$ 理想正方形 $dp$

刷题总结——bzoj1725(状压dp)

P2216 [HAOI2007]理想的正方形