一维ST表
算法思想:
- 定义 f [ i ] [ j ] f[i][j] f[i][j] 为从 i i i 开始的长度为 2 j 2^j 2j 的区间最值。
- 预处理方程: f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + ( 1 < < j − 1 ) ] [ j − 1 ] ) f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]) f[i][j]=max(f[i][j−1],f[i+(1<<j−1)][j−1])。
- 询问方程: 2 k < = l e n , q u e r y ( l , r ) = m a x ( f [ l ] [ k ] , f [ r − ( 1 < < k ) + 1 ] [ k ] ) 2^k<=len ,query(l,r)=max(f[l][k],f[r-(1<<k)+1][k]) 2k<=len,query(l,r)=max(f[l][k],f[r−(1<<k)+1][k])。
- 其中 k k k 可通过 k = _ _ l g ( r − l + 1 ) k=\_\_lg(r-l+1) k=__lg(r−l+1) 实现 O ( 1 ) O(1) O(1) 计算得到 l e n len len 二进制最高位1。
复杂度: 预处理 O ( n l o g n ) O(nlogn) O(nlogn) ,查询 O ( 1 ) O(1) O(1)
模板:
const int N = 2e5 + 10, LG = 20;
int a[N], f[N][LG], n;
void init()
{
for(int j = 0; j < LG; j ++ )
for(int i = 1; i + (1 << j) - 1 <= n; i ++ )
if(!j) f[i][j] = a[i];
else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
int query(int l, int r)
{
int k = __lg(r - l + 1);
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
Acwing 1273 - 天才的记忆
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10, M = 18;
int a[N], f[N][M], n;
void init()
{
for(int j=0; j<18; j++)
for(int i=1; i + (1 << j) - 1 <= n; i++)
if(!j) f[i][j] = a[i];
else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
int query(int l, int r)
{
int k = __lg(r - l + 1);
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
init();
int T; scanf("%d", &T);
while(T -- ){
int l, r; scanf("%d %d", &l, &r);
printf("%d\n", query(l, r));
}
system("pause");
return 0;
}
HDU-5289 Assignment
题意: 问有多少个区间满足区间最大最小值之差小于给定的 k k k 。
思路: 双指针+ST表
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = 17;
int n, k;
#define fmax f_max
#define fmin f_min
int a[N], fmax[N][M], fmin[N][M];
void init()
{
for(int j=0; j<M; j++)
for(int i=1; i + (1 << j) - 1 <= n; i++)
if(!j) fmax[i][j] = fmin[i][j] = a[i];
else fmax[i][j] = max(fmax[i][j - 1], fmax[i + (1 << j - 1)][j - 1]), fmin[i][j] = min(fmin[i][j - 1], fmin[i + (1 << j - 1)][j - 1]);
}
int qmax(int l, int r)
{
int k = __lg(r - l + 1);
return max(fmax[l][k], fmax[r - (1 << k) + 1][k]);
}
int qmin(int l, int r)
{
int k = __lg(r - l + 1);
return min(fmin[l][k], fmin[r - (1 << k) + 1][k]);
}
int main()
{
int T; scanf("%d", &T);
while(T -- ){
scanf("%d%d", &n , &k);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
init();
LL ans = 0;
for(int i=1, j=1; i <=n; i++){
while(j < i && qmax(j, i) - qmin(j, i) >= k) j ++ ;
ans += i - j + 1;
}
printf("%lld\n", ans);
}
system("pause");
return 0;
}
二维ST表
算法思想:
-
预处理: f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k − 1 ] , f [ i + ( 1 < < k − 1 ) ] [ j ] [ k − 1 ] , f [ i ] [ j + ( 1 < < k − 1 ) ] [ k − 1 ] , f [ i + ( 1 < < k − 1 ) ] [ j + ( 1 < < k − 1 ) ] [ k − 1 ] ) f[i][j][k] = max(f[i][j][k-1] , f[i+(1<<k-1)][j][k-1] , f[i][j+(1<<k-1)][k-1] , f[i+(1<<k-1)][j+(1<<k-1)][k-1]) f[i][j][k]=max(f[i][j][k−1],f[i+(1<<k−1)][j][k−1],f[i][j+(1<<k−1)][k−1],f[i+(1<<k−1)][j+(1<<k−1)][k−1])
-
查询: a n s = m a x ( f [ x ] [ y ] [ k ] , f [ x ] [ y + l e n − ( 1 < < k ) ] [ k ] , f [ x + l e n − ( 1 < < k ) ] [ y ] [ k ] , f [ x + l e n − ( 1 < < k ) ] [ y + l e n − ( 1 < < k ) ] [ k ] ) ans=max(f[x][y][k], f[x][y+len-(1<<k)][k],f[x+len-(1<<k)][y][k],f[x+len-(1<<k)][y+len-(1<<k)][k]) ans=max(f[x][y][k],f[x][y+len−(1<<k)][k],f[x+len−(1<<k)][y][k],f[x+len−(1<<k)][y+len−(1<<k)][k])
注意: 二维ST表只能查询正方形区域的最大值最小值,矩形区域无法处理。
P2216 [HAOI2007]理想的正方形
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10, M = 14;
#define fmax f_max
#define fmin f_min // fmax 和 fmin 会重名,要宏定义一下
int n, m, len;
int a[N][N], fmax[N][N][M], fmin[N][N][M];
void init()
{
for(int k=0; k<M; k ++ )
for(int i=1; i + (1 << k) - 1 <= n; i ++ )
for(int j = 1; j + (1 << k) - 1 <= m; j ++ )
if(!k) fmax[i][j][k] = fmin[i][j][k] = a[i][j];
else fmax[i][j][k] = max(max(fmax[i][j][k - 1], fmax[i][j + (1 << k - 1)][k - 1]), max(fmax[i + (1 << k - 1)][j][k - 1], fmax[i + (1 << k - 1)][j + (1 << k - 1)][k - 1])),
fmin[i][j][k] = min(min(fmin[i][j][k - 1], fmin[i][j + (1 << k - 1)][k - 1]), min(fmin[i + (1 << k - 1)][j][k - 1], fmin[i + (1 << k - 1)][j + (1 << k - 1)][k - 1]));
}
int qmax(int x, int y, int len)
{
int k = __lg(len);
return max(max(fmax[x][y][k], fmax[x + len - (1 << k)][y][k]), max(fmax[x][y + len - (1 << k)][k], fmax[x + len - (1 << k)][y + len - (1 << k)][k]));
}
int qmin(int x, int y, int len)
{
int k = __lg(len);
return min(min(fmin[x][y][k], fmin[x + len - (1 << k)][y][k]), min(fmin[x][y + len - (1 << k)][k], fmin[x + len - (1 << k)][y + len - (1 << k)][k]));
}
int main()
{
cin >> n >> m >> len;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++) cin >> a[i][j];
init();
int ans = 2e9;
for(int i=1; i + len - 1 <= n; i++)
for(int j=1; j + len - 1 <=m; j ++ )
ans = min(ans, qmax(i, j, len) - qmin(i, j, len));
cout << ans;
system("pause");
return 0;
}
HDU 5726 GCD
题意: 问定区间中有多少个子区间的 g c d gcd gcd 等于某个区间的 g c d gcd gcd。 ( n < = 1 e 5 , q < = 1 e 5 ) (n<=1e5,q<=1e5) (n<=1e5,q<=1e5)
题解:HDU 5726 GCD
- ST表也可以处理区间 g c d gcd gcd 问题。
- 要注意的性质是:往一个集合中不断加入数字, g c d gcd gcd 的可能个数是 l o g ( 第一个数 ) log(第一个数) log(第一个数) 级别的。
- 因此这道题,枚举定左端点,二分找到块的右端点(块中 g c d gcd gcd相等且块间 g c d gcd gcd是递减的),然后分块预处理答案个数。时间 O ( n l o g a i ) O(nloga_i) O(nlogai)。
AC代码(一遍过太爽辽hh):
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = 17;
int a[N], f[N][M], n;
map<int, LL> ans;
void init()
{
for(int j=0; j<M; j++)
for(int i=1; i + (1 << j) - 1 <= n; i++)
if(!j) f[i][j] = a[i];
else f[i][j] = __gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
int query(int l, int r)
{
int k = __lg(r - l + 1);
return __gcd(f[l][k], f[r - (1 << k) + 1][k]);
}
void get_ans()
{
for(int i=1; i<=n; i++)
{
int now = a[i], end = query(i, n), idx = i;
if(now == end){ // 要保证有两个及以上的块,否则第一次二分会出错
ans[now] += n - i + 1;
continue;
}
while(1)
{
int l = idx, r = n;
while(l < r)
{
int mid = l + r >> 1;
if(query(i, mid) < now) r = mid;
else l = mid + 1;
}
ans[now] += r - idx;
now = query(i, r);
idx = r;
if(now == end){
ans[now] += n - idx + 1;
break;
}
}
}
}
int main()
{
int T; scanf("%d", &T);
for(int _ = 1; _ <= T; _ ++)
{
ans.clear();
int q; scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
init();
get_ans();
printf("Case #%d:\n", _);
scanf("%d", &q);
while(q -- ){
int l, r; scanf("%d%d", &l, &r);
printf("%d %lld\n", query(l, r), ans[query(l, r)]);
}
}
return 0;
}