首页 > 其他分享 >[BZOJ1047][HAOI2007][AcWing1091]理想的正方形(单调队列)

[BZOJ1047][HAOI2007][AcWing1091]理想的正方形(单调队列)

时间:2024-02-21 22:23:08浏览次数:24  
标签:rmin int rmax HAOI2007 AcWing1091 BZOJ1047 ans front d1

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
			while(!d1.empty()&&d1.front()+n<=j)d1.pop_front();//清除那些已经在区间外的数
			while(!d2.empty()&&rmax[d2.back()][i]<=rmax[j][i])d2.pop_back();//维护求最大值的单调栈 栈顶元素小于当前值pop
			while(!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();
			while(!d1.empty()&&d1.front()+n<=j)d1.pop_front();
			while(!d2.empty()&&m[i][d2.back()]<=m[i][j])d2.pop_back();
			while(!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();
			while(!d1.empty()&&d1.front()+n<=j)d1.pop_front();
			while(!d2.empty()&&rmax[d2.back()][i]<=rmax[j][i])d2.pop_back();
			while(!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;
}

标签:rmin,int,rmax,HAOI2007,AcWing1091,BZOJ1047,ans,front,d1
From: https://www.cnblogs.com/LBTL/p/18026336

相关文章

  • P2216 [HAOI2007] 理想的正方形 题解
    题目链接:理想的正方形比较明显的,我们可以用二维ST表解决,具体的二维ST表的实现,只需要知道一点:对于\(st[i][j][t]=max(i\simi+2^t,j\simj+2^t)\),表示的是如图所示的大正方形范围内的最值,它可以拆成从四个小正方形的左端点走\(2^{t-1}\)长的小正方形组成,预处理完直接查......
  • P2216 [HAOI2007] 理想的正方形 题解
    Description给定\(n\timesm\)的矩阵,找大小为\(k\timesk\)的子矩阵\(a\),使得子矩阵\(\max\{a\}-\min\{a\}\)最小。SolutionSolution1枚举所有\(k\timesk\)的子矩阵,然后枚举最大值和最小值,时间复杂度\(O(n^4)\),期望得分\(20\)分。Solution2求最大值和最小......
  • P2215 [HAOI2007] 上升序列
    考虑一个长度为\(L\)的最长上升子序列\(P\),以它的第\(i\)个元素\(a_{x_i}\)开头的最长上升子序列长度至少为\(L-i+1\)。反之,若一个数满足以其开头的最长上升子序列长度至少为\(L-i+1\)则这个数必定可以作为\(P\)的第\(i\)个元素。所以我们可以先倒着跑一遍最长下降......
  • P1463 [POI2001] [HAOI2007] 反素数 题解
    P1463[POI2001][HAOI2007]反素数题解可以发现,最大的不超过\(n\)的反素数就是\(1\simn\)中因数最多的数字。证明:设\(x,x\in[1,n]\)为\(1\simn\)中因数最多的数字,则\(x<y\len\)都不会成为答案,因为存在\(x<y\)因数比\(y\)多,同时也不会存在\(y'<x\)......
  • [HAOI2007]理想的正方形【题解】
    题目描述有一个\(a\timesb\)的整数组成的矩阵,现请你从中找出一个\(n\timesn\)的正方形区域,使得该区域所有数中的最大值和最小值的差最小。输入格式第一行为\(3\)个整数,分别表示\(a,b,n\)的值。第二行至第\(a+1\)行每行为\(b\)个非负整数,表示矩阵中相应位置上......
  • [POI2001][HAOI2007] 反素数 题解
    前置知识:一些关于约数的小常识。唯一分解定理对于所有正整数\(n\),一定有唯一分解方式\(n=p_1^{c_1}p_2^{c_2}\cdotsp_m^{c_m}\),其中\(p_1<p_2<\cdots<p_m\),......