[HAOI]2007 理想的正方形

[HAOI]2007 理想的正方形

本题地址: http://www.luogu.org/problem/show?pid=2216

题目描述

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

输入输出格式

输入格式:
第一行为3个整数,分别表示a,b,n的值
第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。

输出格式:
仅一个整数,为a*b矩阵中所有“n*n正方形区域中的最大整数和最小整数的差值”的最小值。

输入输出样例
输入样例#1:
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
输出样例#1:
1 ———————-(作者注:明显右下角那个嘛= =)
说明
问题规模
(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

题解

拿到这道题 好像是一个维护区间最大最小值? 会不会是二维的线段树什么的= =(虽然我不会写)
-> ->搞不了了 只能O(a*b*n^2)暴力了 ,但是貌似20都没有(好像是有的。。但是没卵用=),去看看神犇们的题解吧.
-> ->然而单调队列是个什么东西 ,好像从来没写过的样子.
-> ->将原矩阵分为b列 每一列进行一次滑动窗口单调队列 窗口长就是n嘛
-> ->然后开数组记下来 比如说Max[i][j]表示从第i行第j-n+1列到第j列的最大值之类的
-> ->这个时候n行被压缩成了一行(只要求最大值嘛) 然后利用Max[i][j]再来一发单调队列 这时候每n个点就被压成了一个点 开数组记下来 比如MAX[i][j]表示以i,j为右下角的n*n的矩形的最大值
-> ->最小值也像上面这样搞就好了 然后暴力(a-n)*(b-n)找最小差值
-> ->其实就是4次单调队列= = 未曾见过如此之神题(也勉强算DP吧?)

代码

(输入数据有一百万)(四次单调队列都差不多,复制即可,只是要注意竖着滑窗口的时候变的是第一维而非第二维)
(不想用deque所以手写了队列 觉得两个队列一起写很蛋疼于是写了4个函数= =)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000+10;
const int INF=(1<<30);

int a,b,n,map[maxn][maxn];
int Max[maxn][maxn],Min[maxn][maxn];
int MAX[maxn][maxn],MIN[maxn][maxn];

int read()
{
    int ret=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) ret=ret*10+ch-'0';
    return ret;
}

void init_data()
{
    cin>>a>>b>>n;
    for(int i=1;i<=a;i++)
       for(int j=1;j<=b;j++)
          map[i][j]=read();
}

void cal0(int x)//  cal max  downer 从上往下竖着压大的
{
    int win[maxn]={0},rear,front;
    win[1]=1;
    rear=front=1;
    for(int i=2;i<=a;i++)
    {
        if(win[rear]<=i-n) rear++;
        if(front==0) win[++front]=i;
        else
        {
            while(front>0&&(map[i][x]>map[win[front]][x]&&front>=rear)) front--;
            win[++front]=i;
        }
        if(i>=n) Max[i][x]=map[win[rear]][x];
    }
}

void cal1(int x)//  cal min upper 从上往下压小的
{
    int win[maxn]={0},rear,front;
    win[1]=1;
    rear=front=1;
    for(int i=2;i<=a;i++)
    {
        if(win[rear]<=i-n) rear++;
        if(front==0) win[++front]=i;
        else
        {
            while(front>0&&(map[i][x]<map[win[front]][x]&&front>=rear)) front--;
            win[++front]=i;
        }
        if(i>=n) Min[i][x]=map[win[rear]][x];
    }
}

void cal2(int x)//利用已经压出来的Max横着压成MAX
{
    int *m=Max[x];
    int win[maxn]={0},rear,front;
    win[1]=1;
    rear=front=1;
    for(int i=2;i<=b;i++)
    {
        if(win[rear]<=i-n) rear++;
        if(front==0) win[++front]=i;
        else
        {
            while(front>0&&(m[i]>m[win[front]]&&front>=rear)) front--;
            win[++front]=i;
        }
        if(i>=n) MAX[x][i]=m[win[rear]];
    }
}

void cal3(int x)//类似
{
    int *m=Min[x];
    int win[maxn]={0},rear,front;
    win[1]=1;
    rear=front=1;
    for(int i=2;i<=b;i++)
    {
        if(win[rear]<=i-n) rear++;
        if(front==0) win[++front]=i;
        else
        {
            while(front>0&&(m[i]<m[win[front]]&&front>=rear)) front--;
            win[++front]=i;
        }
        if(i>=n) MIN[x][i]=m[win[rear]];
    }
}

int main()
{
    init_data();
    for(int i=1;i<=b;i++) cal0(i);
    for(int i=1;i<=b;i++) cal1(i);
    for(int i=n;i<=a;i++) cal2(i);
    for(int i=n;i<=a;i++) cal3(i);
    int ans=INF;
    for(int i=n;i<=a;i++) 
       for(int j=n;j<=b;j++)
          ans=min(ans,MAX[i][j]-MIN[i][j]);
    printf("%d",ans);
    return 0;
} 

DP神题%%%

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值