[BZOJ1047][HAOI2007][AcWing1091] 理想的正方形

image
此题的数据相当大,暴力的显然过不了,即使是O(abn)的算法也会超时,所以只能考虑O(ablogn)或O(ab)的算法。

50分暴力
#include <bits/stdc++.h>
using namespace std;
int n,a,b,m[1001][1001];
int dx(int x,int y){
	int maxn=0,minn=0x7fffffff;
	for(int i=x;i<=x+n-1;++i){
		for(int j=y;j<=y+n-1;++j){
			maxn=max(maxn,m[i][j]);
			minn=min(minn,m[i][j]);
		}
	}
	return maxn-minn;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>a>>b>>n;
	for(int i=1;i<=a;++i){
		for(int j=1;j<=b;++j){
			cin>>m[i][j];
		}
	}
	int ans=0x7fffffff;
	for(int i=1;i<=a-n+1;++i){
		for(int j=1;j<=b-n+1;++j){
			ans=min(ans,dx(i,j));
		}
	}
	cout<<ans;
}

下面是O(ab)的单调队列做法:
显然,枚举是必不可少的,且最坏情况也只能是O(ab);所以我们必须为这个O(ab)的枚举计算一些状态,即以(i,j)为左上角的n×n的正方形区域中的最大值和最小值。
把这个二维的问题化简成一维,就是以(i,j)为左边的长度为n的序列的最大值max[i,j]和最小值min[i,j],然后用同样的推算方法可以由这个一维的状态可以推算出二维的状态,而我们可以借助单调队列了在O(b)的时间内算出一行的max[i]与min[i],用相同的方法可以在O(a)时间内根据max[i]和min[i]计算出正方形的最优解,时间复杂度为O(ab)。

准备&初始化
int a,b,n,ans,m[N][N];//ans记录答案,m存储矩阵
rmax[N][N],rmin[N][N],maxn[N][N],minn[N][N];//rmax行最大,rmin行最小,maxn,整体最大,minn整体最小
deque<int> d1,d2;//分别记录最小和最大值
scanf("%d%d%d",&a,&b,&n);
	for(int i=1;i<=a;i++){
		for(int j=1;j<=b;j++){
			scanf("%d",&m[i][j]);
		}
	}
第一轮:求每行最值 O(b)
for(int i=1;i<=a;i++){
		d1.clear();//清空双端队列
		d2.clear();
		for(int j=1;j<=b;j++){	
			while(!d1.empty()&&m[i][d1.back()]>=m[i][j])d1.pop_back();//维护求最小值的单调栈 栈顶元素大于当前值pop
			if(!d1.empty()&&d1.front()+n<=j)d1.pop_front();//清除那些已经在区间外的数
			while(!d2.empty()&&m[i][d2.back()]<=m[i][j])d2.pop_back();//维护求最大值的单调栈 栈顶元素小于当前值pop
			if(!d2.empty()&&d2.front()+n<=j)d2.pop_front();//清除那些已经在区间外的数
			d1.push_back(j);//当前元素进栈
			d2.push_back(j);
			if(j>=n){//更改rmin与rmax
				rmin[i][j-n+1]=m[i][d1.front()];
				rmax[i][j-n+1]=m[i][d2.front()];
			}
		}
	}
第二轮:求整体最值 O(a)
for(int i=1;i<=b-n+1;i++){
		d1.clear();
		d2.clear();
		for(int j=1;j<=a;j++){
			while(!d1.empty()&&rmin[d1.back()][i]>=rmin[j][i])d1.pop_back();//维护求最小值的单调栈 栈顶元素大于当前值pop
			if(!d1.empty()&&d1.front()+n<=j)d1.pop_front();//清除那些已经在区间外的数
			while(!d2.empty()&&rmax[d2.back()][i]<=rmax[j][i])d2.pop_back();//维护求最大值的单调栈 栈顶元素小于当前值pop
			if(!d2.empty()&&d2.front()+n<=j)d2.pop_front();//清除那些已经在区间外的数
			d1.push_back(j);//当前元素进栈
			d2.push_back(j);
			if(j>=n){//更改minn与maxn
				minn[j-n+1][i]=rmin[d1.front()][i];
				maxn[j-n+1][i]=rmax[d2.front()][i];
			}
		}
	}
完整代码(纯享版)
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a,b,n,m[N][N],rmax[N][N],rmin[N][N],maxn[N][N],minn[N][N],ans;
deque<int> d1,d2;
int main(){
	scanf("%d%d%d",&a,&b,&n);
	for(int i=1;i<=a;i++){
		for(int j=1;j<=b;j++){
			scanf("%d",&m[i][j]);
		}
	}
	for(int i=1;i<=a;i++){
		d1.clear();
		d2.clear();
		for(int j=1;j<=b;j++){	
			while(!d1.empty()&&m[i][d1.back()]>=m[i][j])d1.pop_back();
			if(!d1.empty()&&d1.front()+n<=j)d1.pop_front();
			while(!d2.empty()&&m[i][d2.back()]<=m[i][j])d2.pop_back();
			if(!d2.empty()&&d2.front()+n<=j)d2.pop_front();
			d1.push_back(j);
			d2.push_back(j);
			if(j>=n){
				rmin[i][j-n+1]=m[i][d1.front()];
				rmax[i][j-n+1]=m[i][d2.front()];
			}
		}
	}
	for(int i=1;i<=b-n+1;i++){
		d1.clear();
		d2.clear();
		for(int j=1;j<=a;j++){
			while(!d1.empty()&&rmin[d1.back()][i]>=rmin[j][i])d1.pop_back();
			if(!d1.empty()&&d1.front()+n<=j)d1.pop_front();
			while(!d2.empty()&&rmax[d2.back()][i]<=rmax[j][i])d2.pop_back();
			if(!d2.empty()&&d2.front()+n<=j)d2.pop_front();
			d1.push_back(j);
			d2.push_back(j);
			if(j>=n){
				minn[j-n+1][i]=rmin[d1.front()][i];
				maxn[j-n+1][i]=rmax[d2.front()][i];
			}
		}
	}
	ans=0x7fffffff;
	for(int i=1;i<=a-n+1;i++){
		for(int j=1;j<=b-n+1;j++){
			ans=min(ans,maxn[i][j]-minn[i][j]);
		}
	}
	cout<<ans;
	return 0;
}
转载自@CuFeO4的随机数暴力(模拟退火)代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<ctime>
using namespace std;
int a,b,s[1010][1010],n,t1,t2,mx,mn,ans=1999999999,check[1010][1010];
int main()
{
    int i,j,k;
    srand(time(0));
    cin>>a>>b>>n;
    for(i=1;i<=a;i++)
     	for(j=1;j<=b;j++)
      		scanf("%d",&s[i][j]);
    for(i=1;i<=300000;i++)//没错就是30w 不会超时
    {
        t1=rand()%(a-n+1)+1;
        t2=rand()%(b-n+1)+1;
        if(check[t1][t2]==1) continue;//一些剪枝
        check[t1][t2]=1;
        mn=1999999999;
        mx=0;
        for(j=t1;j<t1+n;j++)
        {
         for(k=t2;k<t2+n;k++)
         {
             if(s[j][k]>mx) mx=s[j][k];
             if(s[j][k]<mn) mn=s[j][k];
             if(mx-mn>ans) break;//一些剪枝
         }
         if(mx-mn>ans) break;
        }
        //cout<<t1<<" "<<t2<<" "<<mx<<" "<<mn<<endl;
        //system("pause");
        ans=min(ans,mx-mn);
    }
    cout<<ans; 
    return 0;
}
posted @ 2024-02-21 22:15  萝卜甜了  阅读(13)  评论(0编辑  收藏  举报