DAY3 倍增学习报告
没学明白就完事了,报什么告这天的题虽然就4道且2道都是模板,但是由于学的太一般了,一道题琢磨了一下午,故认真消化了一下,复习复习,另一道等明天讲完了补做。
/(课堂笔记)
倍增是将一般循环转化成找2的n次方,时间O(logn)。
ST表利用倍增能很快解决查询区间最大/最小值问题。 /
本报告唯一的内容,是通过下面这个问题(又是一个我玩了一下午的题)理解一下ST表的运用:(P2216)
一句话题解:此题是一个二维的ST表 (玩ST表熟练的同志就不用再看下去了) 。
ST表的基本原理是,用一个DP数组存储从第i个数开始,包括它在内的2^j个数的和/最大值/最小值等基本特征,然后对这个DP数组进行查询。
首先,回顾一下ST表的模板:
#include<cstdio>
#include<algorithm>
using namespace std;
long long d[100001],c[100001],dp[100001][101],st[100001];
int main(){
long long n,m,i,j,k = 0,l,r,temp;
scanf("%lld %lld",&n,&m);
for(i = 1;i <= n;i++){
scanf("%lld %lld",&d[i],&c[i]);
dp[i][0] = c[i];
}
for(j = 1;(1 << j) <= n;j++){
for(i = 1;i + (1 << j) - 1 <= n;i++){
dp[i][j] = max(dp[i][j - 1],dp[i + (1 << (j - 1))][j - 1]);
}
}
for(i = 1;i <= n;i++){
if((1 << k) <= i) k++;
st[i] = k - 1;
}
for(i = 1;i <= m;i++){
scanf("%lld %lld",&l,&r);
temp = st[r - l + 1];
printf("%lld\n",max(dp[l][temp],dp[r - (1 << temp) + 1][temp]));
}
return 0;
}
这里比较值得一提的是,此处st存储的是不大于区间长的最大的2^j的j值,用于保证一次查询一定能查到最大值。
接下来看这道题。
参考代码1:(从洛谷上学的)
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int a[1001][1001],b[1001][1001],c[1001][1001],temp,k;
int query(int x,int y){
int ret1,ret2;
ret1 = max(b[x][y],max(b[x + k - (1 << temp)][y],max(b[x][y + k - (1 << temp)],b[x + k - (1 << temp) + 1][y + k - (1 << temp)])));
ret2 = min(c[x][y],min(c[x + k - (1 << temp)][y],min(c[x][y + k - (1 << temp)],c[x + k - (1 << temp) + 1][y + k - (1 << temp)])));
return ret1 - ret2;
}
int main(){
int n,m,i,j,l,res = 2147483647;
scanf("%d %d %d",&n,&m,&k);
for(i = 1;i <= n;i++){
for(j = 1;j <= m;j++){
scanf("%d",&a[i][j]);
b[i][j] = c[i][j] = a[i][j];
}
}
temp = log2(k);
for(l = 0;l < temp;l++){
for(i = 1;i + (1 << l) <= n;i++){
for(j = 1;j + (1 << l) <= m;j++){
b[i][j] = max(b[i][j],max(b[i + (1 << l)][j],max(b[i][j + (1 << l)],b[i + (1 << l)][j + (1 << l)])));
c[i][j] = min(c[i][j],min(c[i + (1 << l)][j],min(c[i][j + (1 << l)],c[i + (1 << l)][j + (1 << l)])));
}
}
}
for(i = 1;i <= n - k + 1;i++){
for(j = 1;j <= m - k + 1;j++){
res = min(res,query(i,j));
}
}
printf("%d",res);
return 0;
}
(我并不会科学的解释这个代码,只能简要的解释一下,如解读有误请指正)
这个采取的是ST表四合一的处理方式,b、c都表示从(i,j)开始k为边长中的最值,先赋值为a,然后再初始处理表示最值,最后按ST表的方式进行查询并合并出结果,只不过查询的次数是二维的。
参考代码2:
#include<cstdio>
#include<algorithm>
using namespace std;
int ma[1001][1001][10],mi[1001][1001][10];
int st[1001],a,b,n,data;
int main(){
int i,j,k,x,y,s;
scanf("%d %d %d",&a,&b,&n);
for(i = 1;i <= a;i++){
for(j = 1;j <= b;j++){
scanf("%d",&data);
ma[i][j][0]=mi[i][j][0]=data;
}
}
st[0] = -1;
for(i = 1;i <= max(a,b);i++) st[i] = st[i >> 1] + 1;
for(i = 1;i <= a;i++){
for(k = 1;k <= 10;k++){
for(j = 1;j + (1 << k) - 1 <= b;j++){
ma[i][j][k]=max(ma[i][j][k - 1],ma[i][j + (1 << k - 1)][k - 1]);
mi[i][j][k]=min(mi[i][j][k - 1],mi[i][j + (1 << k - 1)][k - 1]);
}//向右的ST表
}
}
for(i = 1;i <= a;i++){
for(j = 1;j <= b;j++){
x = j,y = min(j + n - 1,b);
s = st[y - x + 1];
ma[i][j][0]=max(ma[i][x][s],ma[i][y-(1<<s)+1][s]);
mi[i][j][0]=min(mi[i][x][s],mi[i][y-(1<<s)+1][s]);
}//将向下的ST表初状态赋值为向右的ST表
}
for(j = 1;j <= b;j++){
for(k = 1;k <= 10;k++){
for(i = 1;i + (1 << k) - 1 <= a;i++){
ma[i][j][k]=max(ma[i][j][k - 1],ma[i + (1 << k - 1)][j][k - 1]);
mi[i][j][k]=min(mi[i][j][k - 1],mi[i + (1 << k - 1)][j][k - 1]);
}//合并向下的ST表
}
}
int ans = 2147483647;
for(i = 1;i + n - 1<= a;i++){
for(j = 1;j +n - 1<= b;j++){
x = i,y = i + n - 1;
s = st[y - x + 1];
ans = min(ans,max(ma[x][j][s],ma[y - (1 << s) + 1][j][s])-min(mi[x][j][s],mi[y - (1 << s) + 1][j][s]));
}//查询
}
printf("%d\n",ans);
return 0;
}
这一段代码完全是两个一维ST表的合成。首先进行一维ST表的合成,然后再把这个ST表赋值给下一维的初状态,然后进行下一维ST表的合成,就完成了二维ST表,查询方法也与一维ST表相同,只不过需要取n次查询的最小值。这样写比较接近原模板,好理解和模仿,但是空间复杂度较高。
Thank you for reading!