题目链接
题意:给一个数列 a ,包含n个不重复的元素。需要你构造n个s串,对于每一个 si 串,需要满足以下几个条件。
- si串要包含n个元素,每个元素都应取自a数列,且最多取一次。
- si[ 1 ] = i ; 且 si[ j ] < si[ j - 1] (j>=2) ;
- 设pos[ j ]为元素si[ j ] 在a数列的位置, 要保证 | pos[j]-pos[j-1] |≤k (1≤k≤1e5);
- 如果当前位置已经无法再构造了,那么就填 0 ;
- 要求所构造的序列字典序最大
输出每一个 si[j] 序列 非 0 的元素个数。
题解:这一题乍一看很绕,但总结来说就是让我们一直找当前位置 j 前k个元素以及后k个元素 比当前元素 si[ j ] 小的最大元素(最大元素保证字典序最大)。这样描述是不是明了很多。显然直接构造 n 个长度小于等于 n 的串会超时,时间复杂度O(n^2)。而题目只要求输出每一个序列非0元素个数,那么我们是否可以用一个数组保存以每个元素开头的串所能构造的最多元素,从小到大去递归更新所有串的答案。
先过一组样例:用 ans 数组代表每一个数字开头的非0元素个数 初始 ans 数组全为1。
7 2
3 1 4 6 2 5 7
- 1 再找不到比 1 小的 ans[1] = 1;
- 2 合法区间找不到比2小的 ans[2] =1
- 3 1 找到1,ans[3] + = ans[1] = 2
- 4 3 1 找到3就行了,因为3已经是最优的了, ans[4]+=ans[3]。
- 5 2 ans[5]+=ans[2]
- 6 5 2 ans[6]+=ans[5]
- 7 5 2 ans[7]+=ans[5]
所以可以发现每次只要在合法区间找到第一个刚好小于当前元素的最大元素就能依次更新答案,且只要找一次就行了。
这种元素叫做x的前驱元素,其实是一个很难的东西,不带修改的可以用主席树解决,带修改的要用分块解决第三个。那么对于这个题其实线段树就好了,根据位置建线段树,线段树维护区间最大值。每次我只要先查询区间最大值,再将这个点插进去,这样就能保证当前区间的元素一定小于我,且最大。这样这个题就完成了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
int a[N],sz[N],mx[4*N],pos[N];
void up(int id,int l,int r,int pos){
if(l==r){
mx[id]=a[pos];
return;
}
int mid = l+r>>1;
if(pos<=mid) up(id<<1,l,mid,pos);
else up(id<<1|1,mid+1,r,pos);
mx[id]=max(mx[id<<1],mx[id<<1|1]);
}
int query(int id,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){
return mx[id];
}
int mid=l+r>>1,res=0;
if(ql<=mid) res=max(res,query(id<<1,l,mid,ql,qr));
if(qr>mid) res=max(res,query(id<<1|1,mid+1,r,ql,qr));
return res;
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
sz[i]=1;
pos[a[i]]=i;
}
for(int i=1;i<=4*n+10;i++) mx[i]=0;
for(int i=1;i<=n;++i){
int l = max(1,pos[i]-k);//合法区间
int r = min(n,pos[i]+k);
int tmp = query(1,1,n,l,r);//查询
if(tmp!=0) sz[i]+=sz[tmp];//更新答案
up(1,1,n,pos[i]);//插入当前元素
if(i==1) printf("%d",sz[i]);
else printf(" %d",sz[i]);
}
puts("");
}
}