【CF809D】Hitchhiking in the Baltic States

洛谷链接

题目描述

给你长度为 n 的序列,序列中的每个元素 i 有一个区间限制 [li,ri],你从中选出一个子序列,并给它们标号 xi,要求满足 ∀i<j,xi<xj,且 ∀i,xi∈[li,ri]。 问满足条件子序列的长度最长为多少? 1≤n≤3×10^5 , 1≤li≤ri≤10 ^9。

Sol

这是一道毒瘤题(调好久QAQ)

就是让你求一个最长严格上升子序列 , 只不过是每个数有一个取值范围

先想到一个和原来方法类似的dp
d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑到第 i i i个位置 , 最后选择的数是 j j j的最长上升子序列长度

然而这里 j j j的范围很大,显然不可做

我们考虑换维

根据我们最长上身子序列的二分栈的做法 , 很显然的结论是长度一样的上升子序列 , 要最后一个数最小最好,这样才更有可能变长

于是我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑到第 i i i位 , 当前上升子序列的长度为 j j j 的选择的最后一个数的最小值是多少

转移很简单了(注意这里dp实际上是一个前缀max):
假设当前区间是 [ l , r ] [l,r] [l,r]
那么我们就考虑上一位的情况:
首先对任意 j j j , d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j],这样就不用考虑相同 j j j 的转移了, 因为其他的转移和从 j − 1 j-1 j1转移过来是等价的

  1. d p [ i − 1 ] [ j − 1 ] &lt; l dp[i-1][j-1]&lt;l dp[i1][j1]<l , d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , l ) dp[i][j]=min(dp[i][j],l) dp[i][j]=min(dp[i][j],l)
  2. d p [ i − 1 ] [ j − 1 ] &gt; = l &ThickSpace; a n d &ThickSpace; d p [ i − 1 ] [ j − 1 ] &lt; r , d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − 1 ] + 1 ) dp[i-1][j-1]&gt;=l \;and\; dp[i-1][j-1]&lt;r,dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1) dp[i1][j1]>=landdp[i1][j1]<r,dp[i][j]=min(dp[i][j],dp[i1][j1]+1)
  3. d p [ i − 1 ] [ j − 1 ] &gt; = r , 无 转 移 dp[i-1][j-1]&gt;=r , 无转移 dp[i1][j1]>=r,

容易发现情况分为三段

并且我们容易发现一个结论 , 那就是对于一个 i i i , 它的dp值随 j j j的增长而严格单调递增(因为序列严格上升)
显然选的多最后的数就会越大

这样的话dp的转移就是对区间进行操作了,考虑用 S p l a y Splay Splay来维护

情况 1: 这个相当于找到一个小于 l l l的最后一个数的 j j j , 把 j + 1 j+1 j+1赋值为 l l l
情况2 : 这个相当与是找到dp值在 [ l , r ) [l,r) [l,r) 区间内的一段 j j j,把他们的dp值增加1并且下标后移一位(注意可能影响到第三种情况,故要把大于等于 r r r的第一个 j j j给删掉)

插入删除都能做 , 下标后移是什么呢? 其实并不用管 , 因为你会在前面插入一个数 , 下标自动就加1了 , 所以只用 S p l a y Splay Splay实现区间加法就行了

注意要在插入的时候判断是否该dp值已经存在 , 已存在则不满足dp单调的性质 , 要退出不插入

然后就可以愉快地敲起 Splay 了

注意不要被这种数据卡了 QAQ

2999999
1 1000000000
1 1000000000
....

所以每次操作完后随机一个点Splay到根,就可以跑得飞快了…

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<set>
#include<queue>
using namespace std;
inline int read()
{
	int x=0;char ch=getchar();int t=1;
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
	return x*t;
}
const int N=3e5+10;
int n;int l[N],r[N];
#define __ NULL
#define ls son[0]
#define rs son[1]
#define get_size(a) (a? a->size:0)
#define get_son(a) (a->fa->rs==a)
#define get_dp(a) (a? a->dp:-1)
struct node{
	node* son[2];node* fa;int tag;int size;int dp;
	inline void clear(){ls=rs=fa=__;tag=size=dp=0;}
}pool[N];int cnt=0;
node* rt;
inline void update(node* p){p->size=get_size(p->ls)+get_size(p->rs)+1;}
inline void down(node* p)
{
	if(!p) return ;if(!p->tag) return;
	const int x=p->tag;
	if(p->ls) p->ls->tag+=x,p->ls->dp+=x;
	if(p->rs) p->rs->tag+=x,p->rs->dp+=x;
	p->tag=0;return;
}
inline void rotate(node* p){if(!p) return;
	register int k=get_son(p);register node* q=p->fa;
	down(q);down(p);q->son[k]=p->son[k^1];
	if(p->son[k^1]) p->son[k^1]->fa=q;
	if(q->fa) q->fa->son[get_son(q)]=p;
	p->fa=q->fa;q->fa=p;p->son[k^1]=q;
	return update(q);
}
inline void Splay(node* p,node* goal)
{
	if(!p) return;down(p);if(goal==__) rt=p;
	for(;p->fa!=goal;rotate(p)) if(p->fa->fa==goal) continue;else get_son(p)==get_son(p->fa)? rotate(p->fa):rotate(p);
	return update(p);
}
inline node* Find_pre(int X){node* p=rt,*pre=__;while(p) {down(p);if(get_dp(p)>=X) p=p->ls;else pre=p,p=p->rs;} return pre;}
inline node* Find_nxt(int X){node* p=rt,*pre=__;while(p) {down(p);if(get_dp(p)< X) p=p->rs;else pre=p,p=p->ls;} return pre;}
inline void insert(int x)
{
	if(!rt){rt=&pool[cnt++];rt->clear();rt->size=1;rt->dp=x;return;}
	register node* p=rt,*pre=__;
	while(p){
		pre=p;
		if(p->dp==x) return;down(p);
		if(p->dp>x) p=p->ls;else p=p->rs;
	}
	p=&pool[++cnt];p->clear();p->size=1;p->dp=x;p->fa=pre;
	if(pre->dp>x) pre->ls=p; else pre->rs=p;
	return Splay(p,__);
}
inline void Del(node* p)
{
	if(!p) return;Splay(p,__);node* q=p->ls;
	while(q&&q->rs) q=q->rs;
	if(!q) {rt=p->rs;if(p->rs) p->rs->fa=__;p->clear();}
	else {
		Splay(q,__);
		if(p->rs) p->rs->fa=q;if(q) q->rs=p->rs;
		update(q);p->clear();
	}
	return;
}
int main()
{
	srand(19260817);
	n=read();rt=__;
	for(int i=1;i<=n;++i) l[i]=read(),r[i]=read();
	if(!n) return puts("0"),0;
	insert(l[1]);
	for(register int i=2;i<=n;++i) {
		register int L=l[i],R=r[i];
		register node* p=Find_pre(L);
		register node* q=Find_nxt(R);
		Splay(p,__);Splay(q,p);
		if(!p&&!q) {rt->tag+=1;rt->dp+=1;}
		else {
			if(!q) {if(p->rs) p->rs->dp+=1,p->rs->tag+=1;}
			else if(q->ls) q->ls->dp+=1,q->ls->tag+=1;Del(q);
		}
		insert(L);
		p=&pool[rand()%i+1];
		if(p->size) Splay(p,__);
	}
	register node* p=rt;if(!p) return puts("0"),0;
	while(p->rs) p=p->rs;Splay(p,__);
	printf("%d\n",get_size(p->ls)+1);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值