【题解 && 线段树优化DP】Pillars

题目传送门


题目描述:

【题解 && 线段树优化DP】Pillars_第1张图片


Solution

我们根据做 最 长 子 序 列 最长子序列 时的经验设 d p dp dp状态:
f [ i ] f[i] f[i]表示匹配了前 i − 1 i-1 i1个数,且第 i i i个数必须匹配的最大长度

很容易得到以下的转移式: f [ i ] = m a x j = 1 i − 1 f [ j ] + 1    ( ∣ a [ i ] − a [ j ] ∣ > = d ) f[i] = max_{j=1}^{i-1}f[j]+1\ \ (|a[i]-a[j]|>=d) f[i]=maxj=1i1f[j]+1  (a[i]a[j]>=d)

但是直接做肯定会 T T T飞,我们考虑优化

我们将绝对值式子进行讨论以及移项,发现满足条件的 a [ j ] a[j] a[j]会满足以下两个关系式:
a [ j ] < = a [ i ] − d a[j]<=a[i]-d a[j]<=a[i]d a [ j ] > = a [ i ] + d a[j]>=a[i]+d a[j]>=a[i]+d
即满足这两个关系的 a [ j ] a[j] a[j]都有可能作为答案来更新。

其实这两个关系就是以下区间:
( 1 , a [ i ] − d ) (1,a[i]-d) (1,a[i]d) ( a [ i ] + d , M a x ) (a[i]+d,Max) (a[i]+d,Max)
这样就将问题转化为了区间求最值问题,用线段树维护即可。

由于权值过大,空间无法储存,所以我们需要离散化,将离散化后的区间放入线段树中维护即可。


而对于第二问的序列询问,我们只需要在线段树中多加一个状态表示当前区间的最大值是由之前哪个点更新过来的即可。 C h a n g e Change Change时一旦改变最大值,连同当前状态一起改变即可。


Code

#include
using namespace std;

typedef pair < int , int > pii;

const int N = 5e5+10;
int n,d;
map < int , int > M;
int num = 0 , Ans = 0;
int a[N],b[N+N+N] , cnt , f[N];
int La[N] , pr[N];

struct trr{
	int l,r,val,id;
};

struct Tr{
	trr tr[10*N];
	void build(int p,int l,int r){
//		cout<<1<
		tr[p].l = l; tr[p].r = r;
		if (l == r) return;
		int mid = l + r >> 1;
		build(p<<1,l,mid); build(p<<1|1,mid+1,r);
	}
	pii Ask(int p,int L,int R){
		int l = tr[p].l , r = tr[p].r;
		if (L <= l && r <= R) return {tr[p].val,tr[p].id};
		pii Ans; Ans = {0,0};
		int mid = l + r >> 1;
		if (L <= mid) Ans = max(Ans,Ask(p<<1,L,R));
		if (R > mid) Ans = max(Ans,Ask(p<<1|1,L,R));
		return Ans;
	}
	void change(int p,int x,int val,int id){
		int l = tr[p].l , r = tr[p].r;
		if (l == r){
		    if (val <= tr[p].val) return;
		    tr[p].val = val; tr[p].id = id;
		    return;
		}
		int mid = l + r >> 1;
		if (x <= mid) change(p<<1,x,val,id);
		else if (x > mid) change(p<<1|1,x,val,id);
		if (tr[p<<1].val > tr[p<<1|1].val) tr[p].val = tr[p<<1].val , tr[p].id = tr[p<<1].id;
		if (tr[p<<1].val <= tr[p<<1|1].val) tr[p].val = tr[p<<1|1].val , tr[p].id = tr[p<<1|1].id;
	}
}tr;



int main(){
	scanf("%d %d",&n,&d);
	for (int i = 1; i <= n; i++){
		scanf("%d",&a[i]);
		b[++num] = a[i];
		b[++num] = a[i] - d;
		b[++num] = a[i] + d;
	}
	sort(b+1,b+num+1);
	M[b[1]] = ++cnt;
	for (int i = 2; i <= num; i++)
	  if (b[i] != b[i-1]) M[b[i]] = ++cnt;
	tr.build(1,1,num);
	int End = 0;
	for (int i = 1; i <= n; i++){
		f[i] = 1;
		int L = M[a[i]-d] , R = M[a[i]+d];
		pii ans; ans = {0,0};
		ans = max(ans,tr.Ask(1,1,L));
		ans = max(ans,tr.Ask(1,R,num));
		if (ans.first > 0) f[i] = ans.first + 1 , La[i] = ans.second;
		int now = M[a[i]];
		tr.change(1,now,f[i],i);
		Ans = max(Ans,f[i]);
		if (f[i] == Ans) End = i;
	}
	int lenn = 0;
	printf("%d\n",Ans);
	while (End){
		pr[++lenn] = End;
		End = La[End];
	}
	for (int i = lenn; i; i--) printf("%d ",pr[i]);
	return 0;
}

你可能感兴趣的:(线段树,题解,动态规划)