CF474E Pillars (线段树优化DP)

37 篇文章 0 订阅

题目链接

题目大意

给出一个长度为 n n n 的序列 a a a 和一个参数 m m m,要求求出一个最长的子序列,满足相邻元素的差大于等于 m m m

解题思路

本题跟LIS的 O ⁡ ( n log ⁡ n ) \operatorname{O}(n\log n) O(nlogn) 做法非常相似,需要用到桶。

首先,我们要将 a [ i ] a[i] a[i] 离散化。
f [ i ] f[i] f[i] 表示以 a [ i ] a[i] a[i] 结尾的最长满足条件的子序列的长度, p r e [ i ] pre[i] pre[i] 表示 f [ i ] f[i] f[i] f [ p r e [ i ] ] f[pre[i]] f[pre[i]] 转移过来。
朴素转移: f [ i ] = max ⁡ ∣ b [ i ] − b [ j ] ≥ m ∣ { f [ j ] } + 1 f[i]=\max\limits_{|b[i]-b[j]\ge m|}\{f[j]\}+1 f[i]=b[i]b[j]mmax{f[j]}+1
这里的 b [ i ] b[i] b[i] 表示离散化之前的 a [ i ] a[i] a[i]

我们考虑用桶优化:
设桶 b i n [ j ] bin[j] bin[j] 表示以 j j j 结尾的序列的最长长度, p o s [ j ] pos[j] pos[j] 表示与 b i n [ j ] bin[j] bin[j] 对应的结尾元素的编号。
由于 a [ i ] a[i] a[i] 已经被离散化了,我们需要用二分查找出与 i i i 最接近的 l [ i ] , r [ i ] l[i],r[i] l[i],r[i],满足 1 ≤ l [ i ] ≤ i ≤ r [ i ] ≤ n 1\le l[i] \le i \le r[i] \le n 1l[i]ir[i]n,且 a [ i ] − a [ l [ i ] ] ≥ m a[i]-a[l[i]] \ge m a[i]a[l[i]]m a [ r [ i ] ] − a [ i ] ≥ m a[r[i]]-a[i] \ge m a[r[i]]a[i]m

那么,转移方程就变成了 f [ i ] = max ⁡ 1 ≤ j ≤ l [ i ] or ⁡ r [ i ] ≤ j ≤ n { b i n [ j ] } + 1 f[i]=\max\limits_{1 \le j \le l[i] \operatorname{or} r[i] \le j \le n} \{ bin[j] \}+1 f[i]=1jl[i]orr[i]jnmax{bin[j]}+1
由于涉及到区间最值,我们可以用线段树维护,总时间复杂度 O ⁡ ( n log ⁡ n ) \operatorname{O}(n \log n) O(nlogn)
注意每次要更新 b i n bin bin 和 维护 p r e [ i ] pre[i] pre[i]

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int Maxn=100000+10,inf=0x3f3f3f3f;
const int Maxm=Maxn<<2;
int maxv[Maxm],p[Maxm];
long long a[Maxn],b[Maxn];
int f[Maxn],pre[Maxn];
int n,m,idcnt;
map <long long,int> id;
inline long long read()
{
	long long s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
inline void push_up(int k)
{
	if(maxv[k<<1]>maxv[k<<1|1])
	maxv[k]=maxv[k<<1],p[k]=p[k<<1];
	else maxv[k]=maxv[k<<1|1],p[k]=p[k<<1|1];
}
void modify(int k,int l,int r,int pos,int u,int v)
{
	if(l==r)
	{
		maxv[k]=u,p[k]=v;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)modify(k<<1,l,mid,pos,u,v);
	else modify(k<<1|1,mid+1,r,pos,u,v);
	push_up(k);
}
int query(int k,int l,int r,int x,int y,int &pos)
{
	if(x<=l && r<=y)
	{
		pos=p[k];
		return maxv[k];
	}
	int mid=(l+r)>>1,tot,cnt;
	int u,v;tot=cnt=0;
	if(x<=mid)tot=query(k<<1,l,mid,x,y,u);
	if(mid<y)cnt=query(k<<1|1,mid+1,r,x,y,v);
	if(tot>cnt){pos=u;return tot;}
	else {pos=v;return cnt;}
}
void calc(int i,int &x,int &y)
{
	int l=1,r=i;
	if(b[i]-b[l]<m){x=0;goto GG;}
	while(l<r)
	{
		int mid=(l+r)>>1;
		++mid;
		if(b[i]-b[mid]<m)r=mid-1;
		else l=mid;
	}
	x=l;
	GG:
	l=i,r=idcnt;
	if(b[r]-b[i]<m){y=0;goto FF;}
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(b[mid]-b[i]<m)l=mid+1;
		else r=mid;
	}
	y=l;
	FF:;
}
void dfs(int x)
{
	if(pre[x])dfs(pre[x]);
	printf("%d ",x);
}
int main()
{
//	freopen("in.txt","r",stdin);
	n=read(),m=read();
	for(int i=1;i<=n;++i)
	a[i]=b[i]=read();
	sort(b+1,b+1+n);
	for(int i=1;i<=n;++i)
	{
		b[++idcnt]=b[i];
		id[b[idcnt]]=idcnt;
		int j=i+1;
		while(j<=n && b[i]==b[j])++j;
		i=j-1;
	}
	for(int i=1;i<=n;++i)
	{
		int x,y,pos;
		calc(id[a[i]],x,y);
		f[i]=1;
		if(x)
		{
			int tmp=query(1,1,idcnt,1,x,pos);
			if(tmp+1>f[i])
			f[i]=tmp+1,pre[i]=pos;
		}
		if(y)
		{
			int tmp=query(1,1,idcnt,y,idcnt,pos);
			if(tmp+1>f[i])
			f[i]=tmp+1,pre[i]=pos;
		}
		modify(1,1,idcnt,id[a[i]],f[i],i);
	}
	int ans=-1,pos;
	for(int i=1;i<=n;++i)
	if(f[i]>ans)
	ans=f[i],pos=i;
	printf("%d\n",ans);
	dfs(pos);
	putchar('\n');
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值