题解 P2251 【质量检测】
team109
2019-08-09 18:14:47
Update:2019.8.10。更新图片$\color{#cecece}{\textsf{,增设水印专区。}}$
Update:2020.2.19。改错别字;由于洛谷Markdown的一些feature又有了改动,于是还得跟着改。
Update:2020.3.12。Markdown又炸了。并修$\LaTeX$
首先,[$\bold{\Large{\color{#3399dd}{\text{双倍经验警告}}}}$](https://www.luogu.org/problem/P1886)
(弱弱地说一句:尽管ST表不是P1886标算,但还是可以AC的)
$ $
然后,把那题代码搬过来:
```cpp
//前面的部分省略
int st1[1000005],st2[1000005];
int main()
{
int n=read(),k=read(),tmp=log2(k+0.1);
for(int i=1;i<=n;i++)st1[i]=st2[i]=read();
for(int i=1;i<=tmp;i++)
{
for(int j=1;j<=n-(1<<i)+1;j++)
st1[j]=max(st1[j],st1[j+(1<<i-1)]),st2[j]=min(st2[j],st2[j+(1<<i-1)]);
}
for(int i=1;i<=n-k+1;i++)
print(min(st2[i],st2[i+k-(1<<tmp)])),putchar(' ');
putchar('\n');
for(int i=1;i<=n-k+1;i++)
print(max(st1[i],st1[i+k-(1<<tmp)])),putchar(' ');
}
```
$ $
注意不用滚动数组会MLE。或者把st1和st2合在一起,输出st1后再算st2,就不会MLE了。
$ $
*~~话说我有点扯远了,现在讲的可是P2251啊。~~* 不过还是平生第一次见到ST表配滚动数组这种奇怪搭配。大概是我太水了吧。
___
$ $
现在稍微改一下,得到本题代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
register char ch=getchar();
register int x=0;
register bool b=0;
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
(ch=='-')&&(b=1,ch=getchar());
while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
b&&(x=-x);
return x;
}
inline void print(int x)
{
if(!x){putchar('0');return;}
register int cnt=0;
char ch[12];
(x&-2147483648)&&(putchar('-'),x=-x);
while(x)ch[cnt++]=x%10+48,x/=10;
while(cnt--)putchar(ch[cnt]);
}
int st[100005];
int main()
{
int n=read(),k=read(),tmp=log2(k+0.1);
for(int i=1;i<=n;i++)st[i]=read();
for(int i=1;i<=tmp;i++)
for(int j=1;j<=n-(1<<i)+1;j++)//防止溢出
st[j]=min(st[j],st[j+(1<<i-1)]);
for(int i=1;i<=n-k+1;i++)
print(min(st[i],st[i+k-(1<<tmp)])),putchar('\n');
}
```
------------
$ $
### 最后讲一下ST表本身。
ST表是用倍增的思路实现的。
ST表专门用于O(1)处理区间最值的询问。
ST表的定义:
`st[i][j]`表示$\mathtt{a[j]}$到$\mathtt{a[j+2^i-1]}$一共$\mathtt{2^i}$个数的最值。
如图(因为*画图3D*不资瓷上下标,所以就把指数一律改成2和3,这两个是可以在字符表里找到的)。
![404](https://cdn.luogu.com.cn/upload/pic/70754.png)
这样,因为总共n个数,所以一维下标做到`log2(n)`就可以了,所以st数组的一维下标是 `O(logn多一点点)` 的,二维下标是 `O(n多一点点)` 的。空间复杂度O(nlogn)。
然后讲ST表的O(nlogn)预处理。
首先,$\mathtt{st[0][i]=a[i]}$,没有问题。
然后,有$\mathtt{st[i][j]=min(st[i-1][j],st[i-1][j+2^{i-1}])}$。如图。**注意图中字母`l`和数字`1`长得是不一样的。**
![404](https://cdn.luogu.com.cn/upload/pic/70766.png)
一般的ST表还要预处理一个`lg2[1...n]`数组,是这样预处理的:
```cpp
lg2[1]=0;
for(int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1;
```
只是这里不用。(~~lg2==某谷2?~~[滑稽])
这样就预处理完了。
接下来讲查询:
查询闭区间(就是指两个端点都取的到的)l...r,是这样的:
![404](https://cdn.luogu.com.cn/upload/pic/70995.png)
这里就要用到`lg2`数组了。
```cpp
int query(int l,int r)
{
int len=lg2[r-l+1];
return min(st[len][l],st[len][r-(1<<len)+1]);
}
```
最后给出我的代码仓库里的代码:
```cpp
int a[SIZE_],st[SIZE_LOGN][SIZE_],lg2[SIZE_];//SIZE_和SIZE_LOGN是两个我自己定义的宏
void pre(int n)
{
memset(lg2,0,sizeof lg2);
for(int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1;
}
int qry1(int l,int r)
{
int len=lg2[r-l+1];
return max(st[len][l],st[len][r-(1<<len)+1]);
}
void work1(int n)
{
memset(st,-256,sizeof st);
for(int i=1;i<=n;i++)st[0][i]=a[i];
for(int i=1;i<=SIZE_LOG;i++)
for(int j=1;j<=n;j++)st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
int qry2(int l,int r)
{
int len=lg2[r-l+1];
return min(st[len][l],st[len][r-(1<<len)+1]);
}
void work2(int n)
{
memset(st,255,sizeof st);
for(int i=1;i<=n;i++)st[0][i]=a[i];
for(int i=1;i<=SIZE_LOG;i++)
for(int j=1;j<=n;j++)st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
```
$\boxed{\textbf{完}}$