第十一届蓝桥杯模拟赛10 数节目(ST表)
原题
题意:求长度为k且字典序最大的子序列
用了ST表做,相比单调栈多了点代码,但也不是很长。
我们可以用ST表来存区间最值的下标,比如说我们某次要取一个数,就要从上次查询出的最值的后面选,同时为了保证后面的数不会不够选(如果还有k个数没选),我们要保证剩下的区间至少还剩 k 的长度,假设上次选出的值位置在 c ,还剩 k 个数没选,那我们接下来可以从[c+1,n-k+1]的区间范围内开始寻找。
因为一个小细节没处理好,搞了一下午,还用了对拍,气死我了!感觉这题范围再大点,我就要MLE了...
#include<bits/stdc++.h>
using namespace std;
int f[1000005][22],x[1000005],ans[1000005];
int n,k;
void ST_work()
{
for(int i=1; i<=n; i++)
f[i][0]=i;//先初始化为本身
for(int j=1; (1<<j)<=n; j++)
for(int i=1; i+(1<<j)-1<=n; i++)
{
int a=f[i][j-1],b=f[i+(1<<(j-1))][j-1];
if(x[a]>=x[b])//这里一定要注意,一定是大于等于,因为如果两个数相等,我们要选前面一个,这样保证后面那个一样大小的数在需要时也能被选到
f[i][j]=a;
else
{
f[i][j]=b;
}
}
}
int ask(int l,int r)
{
int n=log(r-l+1)/log(2);
int a=f[l][n],b=f[r-(1<<n)+1][n];
if(x[a]>=x[b])//同上
return a;
else
return b;
}
int main()
{
while(cin>>n>>k)
{
memset(f,0,sizeof(f));
for(int i=1; i<=n; i++)
scanf("%d",&x[i]);
ST_work();
int s=1,e=n-k+1,cot=0,c;
while(k--)
{
c=ask(s,e);
ans[cot++]=c;
s=c+1;//从后面找
e++;//尾指针也后移,类似滑动窗口
}
for(int i=0; i<cot; i++)
{
cout<<x[ans[i]];
if(i!=cot-1)
cout<<' ';
}
puts("");
}
return 0;
}
戒骄戒躁,百炼成钢!