ST 表
simple_child · · 算法·理论
上课没认真听,现在忘了,重学一下。
ST 表(Sparse Table,稀疏表)是用于解决 可重复贡献问题 的数据结构。
::::info[什么是可重复贡献问题]{open}
可重复贡献问题 是指对于运算
模板题。
考虑暴力。时间复杂度
考虑线段树区间查询最大值。时间复杂度
那为什么这题是个黄题呢?注意到这道题是求静态区间最大值,而线段树还支持区间修改操作。
考虑离线。我们可以仿照前缀和的预处理思路来尝试解决这道题。
基于倍增思想,我们考虑如何求出区间最大值。
可以发现,如果按照一般的倍增流程,每次跳
设
- 初始化:当
i=0 时,区间长度为1 ,最大值就是元素本身。 - 长度为
2ⁱ 的区间可拆成两个长度为2ⁱ⁻¹ 的子区间,因此\mathrm{dp}[i][j] = \max(\mathrm{dp}[i-1][j],\mathrm{dp}[i-1][j + 2ⁱ⁻¹]) 。 :::: 那我们重新回归问题的根本。发现\max(x,x)=x ,即这个问题是一个 可重复贡献问题。
那该如何求解呢?
| 我们把把所有区间长度为二的幂次的最值存入一个表中,就可以在恒定的时间回答任何查找范围。例如下表: ::cute-table{tuack} | |||||
|---|---|---|---|---|---|
| 1 | |||||
| 2 | |||||
| 3 | |||||
| 4 | |||||
| 5 | |||||
| 6 | |||||
| 7 | |||||
| 8 | |||||
| 9 | |||||
| 10 | |||||
| 11 | |||||
| 12 |
令
显然
根据定义式,第二维就相当于倍增的时候「跳了
::::info[如果你还不太理解这个状态转移方程] 我们可以先把每一项展开:
观察到第三个式子还可以进一步化简,我们合并两个
所以说
再给出 OI-Wiki 上的图:
:::align{center}
:::
::::
综上,我们就可以写出预处理每个区间最大值的代码:
for(int i=1;i<=n;i++) f[i][0]=a[i];
for(int j=1;j<=log2(n);j++)
{
//1<<j=2^j 1<<(j-1)=2^{j-1}
for(int i=1;i<=n-(1<<j)+1;i++)
{
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
时间复杂度为
考虑第一重循环。
注意到第一重循环枚举的是 std::log2 时间复杂度]
这一点在 OI-Wiki 上有一定争议,OI-Wiki 认为我们需要预处理一个数组
但是预处理的时间复杂度为
同时,std::log2 的时间复杂度为
因为 i<=n-(1<<j)+1,自己推一下就行了。
:::::
接下来我们只需要将每个查询的区间拆成若干的已处理出的区间即可。
当询问任意区间
此时,从
很容易注意到,即使区间有重复部分,也不会影响最终整个区间的结果。我们只需要在这两个最大值区间内取一个更大的数即可。
:::info[正确性证明]{open}
为什么中间有重复部分是正确的,很明显,我们先令
我们注意到
综上,我们可以写出代码:
int l,r;
for(int i=1;i<=m;i++)
{
cin>>l>>r;
int k=log2(r-l+1);
cout<<max(f[l][k],f[r-(1<<k)+1][k])<<endl;
}
时间复杂度
注意一点,容易让人走不出去,这里的
整体代码如下,因为模板题比较卡常,所以有一些细微优化。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10,MAXJ=log2(MAXN)+10;
int f[MAXN][MAXJ];
int a[MAXN];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
//ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) f[i][0]=a[i];
int t=log2(n);
for(int j=1;j<=t;++j)
{
//1<<j=2^j 1<<(j-1)=2^{j-1}
for(int i=1;i<=n-(1<<j)+1;++i)
{
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
int l,r;
for(int i=1;i<=m;++i)
{
l=read(),r=read();
int k=log2(r-l+1);
cout<<max(f[l][k],f[r-(1<<k)+1][k])<<'\n';
}
return 0;
}
P8818 [CSP-S 2022] 策略游戏
小 L 在
肯定先思考小 Q 再思考 小 L,因为小 L 会思考小 Q 的思考。
小 Q 的行为就是对于
自然的,
那么小 L 的行为是什么呢?还是按照正负分类讨论:
如果小 L 这次想让
如果小 L 这次想让
因此小 L 的行为只有四种:选择最大的
分别讨论小 L 选择四种行为时小 Q 的选择,答案取最大值即可。
本题要做
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int MALN=log2(MAXN)+5;
const long long inf=LONG_LONG_MAX,minn=LONG_LONG_MIN;
long long amx[MAXN][MALN],amn[MAXN][MALN],afx[MAXN][MALN],azn[MAXN][MALN];
long long bmx[MAXN][MALN],bmn[MAXN][MALN];
long long a,b;
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
{
cin>>a;
amx[i][0]=amn[i][0]=a,afx[i][0]=(a<0?a:minn),azn[i][0]=(a>=0?a:inf);
}
for(int i=1;i<=m;i++)
{
cin>>b;
bmx[i][0]=bmn[i][0]=b;
}
int ln=log2(n),lm=log2(m);
for(int j=1;j<=ln;j++)
{
for(int i=1;i<=n-(1<<j)+1;i++)
{
int jj=j-1;
int p=i+(1<<(jj));
amx[i][j]=max(amx[i][jj],amx[p][jj]);
afx[i][j]=max(afx[i][jj],afx[p][jj]);
amn[i][j]=min(amn[i][jj],amn[p][jj]);
azn[i][j]=min(azn[i][jj],azn[p][jj]);
}
}
for(int j=1;j<=lm;j++)
{
for(int i=1;i<=m-(1<<j)+1;i++)
{
int jj=j-1;
int p=i+(1<<(j-1));
bmx[i][j]=max(bmx[i][jj],bmx[p][jj]);
bmn[i][j]=min(bmn[i][jj],bmn[p][jj]);
}
}
int l1,r1,l2,r2;
while(q--)
{
cin>>l1>>r1>>l2>>r2;
int lo1=log2(r1-l1+1),lo2=log2(r2-l2+1);
int x=r1-(1<<lo1)+1,y=r2-(1<<lo2)+1;
long long mamx=max(amx[l1][lo1],amx[x][lo1]);
long long mamn=min(amn[l1][lo1],amn[x][lo1]);
long long mafx=max(afx[l1][lo1],afx[x][lo1]);
long long mazn=min(azn[l1][lo1],azn[x][lo1]);
long long mbmx=max(bmx[l2][lo2],bmx[y][lo2]);
long long mbmn=min(bmn[l2][lo2],bmn[y][lo2]);
long long ans=minn;
ans=max(ans,mamx*(mamx>=0?mbmn:mbmx));
ans=max(ans,mamn*(mamn>=0?mbmn:mbmx));
if(mafx!=minn) ans=max(ans,mafx*(mafx>=0?mbmn:mbmx));
if(mazn!=inf) ans=max(ans,mazn*(mazn>=0?mbmn:mbmx));
cout<<ans<<endl;
}
return 0;
}