** P2216 [HAOI2007]理想的正方形**
首先当然考虑暴力
确定左上角后就搜一遍整个正方形,时间复杂度O(abn^2),20pts
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,m,s;
int a[N][N];
int ans=2147483647;
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i+s-1<=n;i++){
for(int j=1;j+s-1<=m;j++){
//枚举左上角
int maxn=0,minn=2147483647;
for(int k=i;k<=i+s-1;k++){
for(int l=j;l<=j+s-1;l++){
maxn=max(maxn,a[k][l]);
minn=min(minn,a[k][l]);
}
}
if(maxn-minn<ans) ans=maxn-minn;
}
}
cout << ans << endl;
return 0;
}
20分够了,知足吧!
我们怎么能不思进取?
考虑优化
搞个前缀和一样的东西,与处理一下
时间复杂度O(abn) 70pts
#include<bits/stdc++.h>
using namespace std;
#define INF 2147483647
const int N=1005;
int n,m,s;
int a[N][N];
int f[N][N][2];//f[i][j][0,1]表示i,j点向下s个点之间最大值 /最小值
int ans=INF;
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i+s-1<=n;i++){
for(int j=1;j<=m;j++){
//枚举左上角
f[i][j][0]=0;
f[i][j][1]=INF;
for(int k=i;k<=i+s-1;k++){
f[i][j][0]=max(f[i][j][0],a[k][j]);
f[i][j][1]=min(f[i][j][1],a[k][j]);
}
}
}
for(int i=1;i+s-1<=n;i++){
for(int j=1;j+s-1<=m;j++){
//枚举左上角
int maxn=0,minn=INF;
for(int k=j;k<=j+s-1;k++){
maxn=max(maxn,f[i][k][0]);
minn=min(minn,f[i][k][1]);
}
if(maxn-minn<ans) ans=maxn-minn;
}
}
cout << ans << endl;
return 0;
}
这是湖南的省选了,70分很不错了
我们再优化
考虑用线段树
由于是静态的,ST表其实更好
时间复杂度 O(ab log (max(a,b)))
100pts!
可惜我只有50pts(因为本人懒,不想打lazzy tag)
//超长版线段树做法
#include<bits/stdc++.h>
using namespace std;
#define INF 2147483647
#define ri register int
const int N=1005;
int n,m,s;
int a[N][N];
int f[N][N][2];
int t[N*8][2];
int ans=INF;
void build1(int d,int l,int r,int sum){
if(l==r){
t[sum][0]=a[d][l];
t[sum][1]=a[d][l];
return;
}
int mid=(l+r)>>1;
build1(d,l,mid,sum<<1);
build1(d,mid+1,r,(sum<<1)+1);
t[sum][0]=min(t[sum<<1][0],t[(sum<<1)+1][0]);
t[sum][1]=max(t[sum<<1][1],t[(sum<<1)+1][1]);
return;
}
int serch_min(int d,int dl,int dr,int l,int r,int sum){
if(r<dl||l>dr) return INF;
if(l>=dl&&r<=dr) return t[sum][0];
int mid=(l+r)>>1;
int n1=sum<<1,n2=(sum<<1)+1;
return min(serch_min(d,dl,dr,l,mid,n1),serch_min(d,dl,dr,mid+1,r,n2));
}
int serch_max(int d,int dl,int dr,int l,int r,int sum){
if(r<dl||l>dr) return 0;
if(l>=dl&&r<=dr) return t[sum][1];
int mid=(l+r)>>1;
int n1=sum<<1,n2=(sum<<1)+1;
return max(serch_max(d,dl,dr,l,mid,n1),serch_max(d,dl,dr,mid+1,r,n2));
}
void build212(int d,int l,int r,int sum){
if(l==r){
t[sum][0]=f[l][d][0];
t[sum][1]=f[l][d][1];
return;
}
int mid=(l+r)>>1;
build212(d,l,mid,sum<<1);
build212(d,mid+1,r,(sum<<1)+1);
t[sum][0]=min(t[sum<<1][0],t[(sum<<1)+1][0]);
t[sum][1]=max(t[sum<<1][1],t[(sum<<1)+1][1]);
return;
}
int serch_minn(int d,int dl,int dr,int l,int r,int sum){
if(r<dl||l>dr) return INF;
if(l>=dl&&r<=dr) return t[sum][0];
int mid=(l+r)>>1;
int n1=sum<<1,n2=(sum<<1)+1;
return min(serch_minn(d,dl,dr,l,mid,n1),serch_minn(d,dl,dr,mid+1,r,n2));
}
int serch_maxn(int d,int dl,int dr,int l,int r,int sum){
if(r<dl||l>dr) return 0;
if(l>=dl&&r<=dr) return t[sum][1];
int mid=(l+r)>>1;
int n1=sum<<1,n2=(sum<<1)+1;
return max(serch_maxn(d,dl,dr,l,mid,n1),serch_maxn(d,dl,dr,mid+1,r,n2));
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(ri i=1;i<=n;i++){
for(ri j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
//第一次计算:计算f数组
for(ri i=1;i<=n;i++){
memset(t,0,sizeof(t));
build1(i,1,n,1);
for(ri j=1;j+s-1<=m;j++){
f[i][j][0]=serch_min(i,j,j+s-1,1,n,1);
f[i][j][1]=serch_max(i,j,j+s-1,1,n,1);
}
}
for(ri j=1;j+s-1<=m;j++){
memset(t,0,sizeof(t));
build212(j,1,n,1);
for(ri i=1;i+s-1<=n;i++){
int maxn,minn;
minn=serch_minn(j,i,i+s-1,1,n,1);
maxn=serch_maxn(j,i,i+s-1,1,n,1);
ans=min(ans,maxn-minn);
}
}
cout << ans << endl;
return 0;
}
等等,刚那O(abn)算法有优化!
来!
#include<bits/stdc++.h>
using namespace std;
#define INF 2147483647
#define rg register
const int N=1005;
int n,m,s;
int a[N][N];
int f[N][N][2];//f[i][j][0,1]表示i,j点向下s个点之间最大值 /最小值
int ans=INF;
int min(int a,int b){
return a<b?a:b;
}
int max(int a,int b){
return a>b?a:b;
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(rg int i=1;i<=n;i++){
for(rg int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(rg int i=1;i+s-1<=n;i++){
for(rg int j=1;j<=m;j++){
//枚举左上角
f[i][j][0]=0;
f[i][j][1]=INF;
for(int k=i;k<=i+s-1;k++){
f[i][j][0]=max(f[i][j][0],a[k][j]);
f[i][j][1]=min(f[i][j][1],a[k][j]);
}
}
}
for(rg int i=1;i+s-1<=n;i++){
for(rg int j=1;j+s-1<=m;j++){
//枚举左上角
int maxn=0,minn=INF;
for(rg int k=j;k<=j+s-1;k++){
maxn=max(maxn,f[i][k][0]);
minn=min(minn,f[i][k][1]);
}
if(maxn-minn<ans) ans=maxn-minn;
}
}
cout << ans << endl;
return 0;
}
虽然还是70pts,但是快了300ms!
再优化?
O(ab)可不可以?
可以!
单调队列!
首先用单调队列的思想预处理出上面程序的f数组
再用第二次单调队列计算正方形最大最小值
100pts
std:
#include<bits/stdc++.h>
using namespace std;
#define INF 2147483647
#define rg register
const int N=1005;
int n,m,s;
int a[N][N];
int f[N][N][2];//0小1大
int ans=INF;
int h1,t1;//2小1大
int q1[N];
int h2,t2;
int q2[N];
int min(int a,int b){
return a<b?a:b;
}
int max(int a,int b){
return a>b?a:b;
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(rg int i=1;i<=n;i++){
for(rg int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
for(rg int j=1;j<=m;j++){
h1=1,t1=0;
h2=1,t2=0;
for(rg int i=1;i<s;i++){
while(h1<=t1&&a[q1[t1]][j]<=a[i][j]) t1--;
q1[++t1]=i;
while(h2<=t2&&a[q2[t2]][j]>=a[i][j]) t2--;
q2[++t2]=i;
}
for(rg int i=s;i<=n;i++){
while(h1<=t1&&a[q1[t1]][j]<=a[i][j]) t1--;
while(h1<=t1&&q1[h1]<i-s+1) h1++;
q1[++t1]=i;
f[i-s+1][j][1]=a[q1[h1]][j];
while(h2<=t2&&a[q2[t2]][j]>=a[i][j]) t2--;
while(h2<=t2&&q2[h2]<i-s+1) h2++;
q2[++t2]=i;
f[i-s+1][j][0]=a[q2[h2]][j];
}
}
for(rg int i=1;i+s-1<=n;i++){
h1=1,t1=0;
h2=1,t2=0;
for(rg int j=1;j<s;j++){
while(h1<=t1&&f[i][q1[t1]][1]<=f[i][j][1]) t1--;
q1[++t1]=j;
while(h2<=t2&&f[i][q2[t2]][0]>=f[i][j][0]) t2--;
q2[++t2]=j;
}
for(rg int j=s;j<=m;j++){
while(h1<=t1&&f[i][q1[t1]][1]<=f[i][j][1]) t1--;
while(h1<=t1&&q1[h1]<j-s+1) h1++;
q1[++t1]=j;
int maxn=f[i][q1[h1]][1];
while(h2<=t2&&f[i][q2[t2]][0]>=f[i][j][0]) t2--;
while(h2<=t2&&q2[h2]<j-s+1) h2++;
q2[++t2]=j;
int minn=f[i][q2[h2]][0];
ans=min(ans,maxn-minn);
}
}
cout << ans << endl;
return 0;
}
一道水题硬生生做成了难题
我是疯了不是