[HAOI]2007 理想的正方形
题目描述
有一个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神题%%%