首先考虑暴力的做法——很简单,枚举正方形的左上角,然后用
的方法求出最大值和最小值更新答案即可。
然而这样做的时间复杂度是 ,一定会超时。
考虑如何优化, 是底线,只能想方设法把 省略掉。
我们记 和 分别表示 中的最小和最大值。求出 和 后,记 表示 中的最小值, 表示 中的最大值。
我们可以发现 都可以用 的算法求出,而 就是以 为左上角的正方形的最小最大值。
总的时间复杂度是 。代码中的定义稍有不同,在代码中也有解释(其实就是把 变成了以 为右下角的正方形的最小最大值而已)。
const int N=1020;
int P_min[N][N],P_max[N][N];
int F_min[N][N],F_max[N][N];
int ans,n,m,k,q[N],l,r,a[N][N];
//P_min[i][j]=min{a[i][v](j-k+1<=v<=j)}
//P_max[i][j]=max{a[i][v](j-k+1<=v<=j)}
//F_max[i][j]=max{P_max[v][j](i-k+1<=v<=i)}
//F_min[i][j]=min{P_min[v][j](i-k+1<=v<=i)}
int main(){
// freopen("t1.in","r",stdin);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++){
memset(q,0,sizeof(q));l=1;r=0;
for(int j=1;j<=m;j++){
while (l<=r&&q[l]<=j-k) l++;
while (l<=r&&a[i][q[r]]>=a[i][j]) r--;
q[++r]=j;if (j>=k) P_min[i][j]=a[i][q[l]];
}
memset(q,0,sizeof(q));l=1;r=0;
for(int j=1;j<=m;j++){
while (l<=r&&q[l]<=j-k) l++;
while (l<=r&&a[i][q[r]]<=a[i][j]) r--;
q[++r]=j;if (j>=k) P_max[i][j]=a[i][q[l]];
}
}
for(int j=k;j<=m;j++){
memset(q,0,sizeof(q));l=1;r=0;
for(int i=1;i<=n;i++){
while (l<=r&&q[l]<=i-k) l++;
while (l<=r&&P_min[q[r]][j]>=P_min[i][j]) r--;
q[++r]=i;if (i>=k) F_min[i][j]=P_min[q[l]][j];
}
memset(q,0,sizeof(q));l=1;r=0;
for(int i=1;i<=n;i++){
while (l<=r&&q[l]<=i-k) l++;
while (l<=r&&P_max[q[r]][j]<=P_max[i][j]) r--;
q[++r]=i;if (i>=k) F_max[i][j]=P_max[q[l]][j];
}
}
ans=0x3f3f3f3f;
for(int i=k;i<=n;i++)
for(int j=k;j<=m;j++)
ans=min(ans,F_max[i][j]-F_min[i][j]);
printf("%d\n",ans);
return 0;
}
首先我们来看一个贪心的思路:只要交换后能使原字符串的字典序更小,我们就交换,直到不能交换为止,比如这样:
const int N=1e5+100;
char s[N];int n;bool flag;
int main(){
// freopen("t1.in","r",stdin);
scanf("%s",s+1);
n=strlen(s+1);
do{
flag=false;
for(int i=1;i<n;i++)
if (s[i]-s[i+1]==1){
swap(s[i],s[i+1]);
flag=true;
}
}while(flag);
printf("%s",s+1);
return 0;
}
然而这样做是有反例的:
比如原字符串是201
,我们通过肉眼的模拟可以知道答案应该是120
,但是在第一步交换1
和0
时,我们的程序认为它使得原字符串的字典序变大了,以至于没有交换而退出了程序。
所以,我们只能另寻思路。我们发现1
是可以到达天涯海角的,所以我们把1
全部提出来,剩下一个只有0
和2
的字符串,它是不可以交换的。但是我们可以把所有的1
全部插入第一个2
以前,这样得出来的答案一定是字典序最小的。
在我的第二个代码中用到了 ,这里解释一下一些函数,大家可以通过类比的思想知道它们的用途:
-
s.find('2')
:在字符串 中寻找第一个2
的位置,若没有,则返回string::npos
,即s.npos
。 -
s.insert(l,s1)
:在 的第 个字符前插入 。 -
s+s1,s+r[i]
:第一个语句相当于把 插入在 的末尾,第二个语句相当于把 插入在 的末尾,由此可见,如果要把一个字符串或字符插入在另一个字符串的末尾,就可以使用字符串的加法运算。但请注意,单单写s+s1
并没有完成真正的插入操作,s=s+s1
才完成了插入的操作。
最后,我给大家一个第一个程序无法通过但第二个程序可以通过的样例:
输入:2010
,输出:1200