题目大意
在元素互不相同的数列 a a a 中选出一个长度为 m m m 的元素互不相邻的子列,使得子列的极差最小。
思路
爆搜、 d p dp dp 肯定是过不了的,所以我们考虑固定某个值,赛时想到了固定最大或者最小值,然后找到另一个值,但是除了 d p dp dp 没想到好做法,比赛结束了才知道正解居然是同时固定最大和最小值。
当我们固定最大和最小值时,先从大小考虑,在这中间的数一定都能选,那么我们就只用算出用这些数可以凑出多长的不相邻的序列。
但是无论是枚举最大和最小值,还是计算最长序列的长度,复杂度都过高,所以我们还需要进一步优化。
首先是选取最大和最小值上面,我们可以考虑先对原序列进行排序,然后用双指针逐步加数至满足序列长度达到要求,统计答案后再右移左指针,然后右移右指针直到新的序列长度也满足条件。
那么我们可以考虑只计算加入一个新数字,对最长序列长度的影响。
-
若新加入的数字在原序列前后都没有选择的数字,那么加入进来,最长序列长度必定加 1 1 1。
-
若新加入的数字只有有一边有选择的数字,那么只要这连续的数字一共有偶数个,那么也可以让长度加 1 1 1。
-
若新加入的数字两边都有选择的数字,则两边数字个数都是偶数个,长度才能加 1 1 1。
那么我们考虑用线段树维护前后缀长度,每次判断新加入的数字前面的后缀与后面的前缀即可。
AC 代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{int pre,suf,l,r,len;}t[400005];
struct deng{int v,id;}a[100005];
inline bool cmp(deng a,deng b){return a.v<b.v;}
int n,m,ans=0x3f3f3f3f,l,r,siz,qian,hou;
/*线段树部分*/
inline node pushup(node &p,node pl,node pr)
{
p.len=pl.len+pr.len;
p.pre=(pl.pre==pl.len)?pl.len+pr.pre:pl.pre;
p.suf=(pr.suf==pr.len)?pr.len+pl.suf:pr.suf;
return p;
}
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r,t[p].len=t[p].r-t[p].l+1;
if(l==r) return;
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
}
void update(int p,int x,int k)
{
if(t[p].l==t[p].r){t[p].pre=t[p].suf=k;return;}
int mid=t[p].l+t[p].r>>1;
if(mid>=x) update(p<<1,x,k);
else update(p<<1|1,x,k);
pushup(t[p],t[p<<1],t[p<<1|1]);
}
node mer_l(int p,int x)//查询x左侧的后缀和
{
if(t[p].r<=x) return t[p];
int mid=t[p].l+t[p].r>>1;node k;
if(mid>=x) return mer_l(p<<1,x);
else return pushup(k,t[p<<1],mer_l(p<<1|1,x));
}
node mer_r(int p,int x)//查询x右侧的前缀和
{
if(t[p].l>=x) return t[p];
int mid=t[p].l+t[p].r>>1;node k;
if(mid<x) return mer_r(p<<1|1,x);
if(mid>=x) return pushup(k,mer_r(p<<1,x),t[p<<1|1]);
}
int main()
{
scanf("%d%d",&n,&m);
build(1,1,n);
for(int i=1;i<=n;i++) scanf("%d",&a[i].v),a[i].id=i;
sort(a+1,a+n+1,cmp);
for(l=1;l<=n;l++)
{
while(siz<m&&r<n)//右指针右移至最长长度满足题意为止
{
r++,update(1,a[r].id,1);//代表该数可选
/*计算前后缀*/
qian=(a[r].id==1)?0:mer_l(1,a[r].id-1).suf;
hou=(a[r].id==n)?0:mer_r(1,a[r].id+1).pre;
/*判断序列能否增加长度*/
if(!hou&&!qian) siz++;
else if(!hou&&qian&&qian%2==0||hou&&!qian&&hou%2==0) siz++;
else if(hou&&qian&&hou%2==0&&qian%2==0) siz++;
}
if(siz==m)//满足条件
{
if(ans>a[r].v-a[l].v) ans=min(ans,a[r].v-a[l].v);//更新答案
update(1,a[l].id,0);//把这个数去掉
qian=(a[l].id==1)?0:mer_l(1,a[l].id-1).suf;
hou=(a[l].id==n)?0:mer_r(1,a[l].id+1).pre;
if(!hou&&!qian) siz--;
else if(!hou&&qian&&qian%2==0||hou&&!qian&&hou%2==0) siz--;
else if(hou&&qian&&hou%2==0&&qian%2==0) siz--;//同上,不过这里是计算去掉是否影响长度
}
}
printf("%d",ans);
return 0;
}