把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF809D】Hitchhiking in the Baltic States(平衡树优化DP)

题目链接

  • 有一个长度为 \(n\) 的正整数序列,其中 \(a_i\in[l_i,r_i]\)
  • 对于所有可能的序列,求最长严格上升子序列的最大值。
  • \(1\le n\le3\times10^5\)\(1\le l_i\le r_i\le10^9\)

平衡树优化 DP

根据最长上升子序列的一般套路,设 \(f_{i,j}\) 表示前 \(i\) 位中长度为 \(j\) 的严格上升子序列最后一位的最小值。

一个显而易见的性质,\(f_{i,j}\) 随着 \(j\) 的增大一定严格递增。(因为长度为 \(j\) 的严格上升子序列的前 \(j-1\) 位必然可以作为长度为 \(j-1\) 的严格上升子序列)

假设填入一个数 \(x\),则 \(f_{i,j}\) 可以选择等于 \(f_{i-1,j}\),也可以在满足 \(f_{i-1,j-1} < x\) 的前提下选择等于 \(x\)

现在可以填入 \([l,r]\) 中的任意一个数,我们从数组中抠出满足 \(f_{i,j}\)\([l,r)\) 范围内的部分(假设为 \([p,q]\)),则:

  • 对于 \(j=1\sim p-1\),因为 \(f_{i-1,j} < l\),必有 \(f_{i,j}=f_{i-1,j}\)
  • 对于 \(j=p\),满足 \(f_{i-1,j-1} < l\),所以 \(f_{i,j}=l\)
  • 对于 \(j=p+1\sim q+1\),因为 \(f_{i-1,j}\ge f_{i-1,j-1}+1\),且 \(f_{i-1,j-1}+1\) 是可以填的数,因此 \(f_{i,j}=f_{i-1,j-1}+1\)
  • 对于 \(j=q+2\sim n\),因为 \(f_{i-1,j-1} \ge r\),只能 \(f_{i,j}=f_{i-1,j}\)

发现等价于在第 \(p\) 位插入一个新数 \(l\),将原本的 \([p,q]\) 部分右移一位并加 \(1\),删去原本的第 \(q+1\) 位。

可以用平衡树实现这一过程。

代码:\(O(n\log n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 300000
#define INF (int)2e9
using namespace std;
int n;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
class FHQTreap
{
	private:
		#define Rd() (sd=(324682339ull*sd+456789001)%998244353)
		#define SX O[k].S[1],O[x].S[1],y
		#define SY O[k].S[0],x,O[y].S[0]
		#define PD(x) (O[x].F&&(O[x].S[0]&&T(O[x].S[0],O[x].F),O[x].S[1]&&T(O[x].S[1],O[x].F),O[x].F=0))
		#define T(x,v) (O[x].V+=v,O[x].F+=v)
		int sd,rt,Nt;struct node {int D,V,F,S[2];}O[2*N+5];
		I int New(CI v) {return O[++Nt].D=Rd(),O[Nt].V=v,Nt;}
		I void Mg(int& k,RI x,RI y) {if(!x||!y) return (void)(k=x|y);O[x].D>O[y].D?(k=x,PD(x),Mg(SX)):(k=y,PD(y),Mg(SY));}//合并
		I void Sp(RI k,int& x,int& y,CI v) {if(!k) return (void)(x=y=0);PD(k),O[k].V<v?(x=k,Sp(SX,v)):(y=k,Sp(SY,v));}//分裂成小于v和大于等于v的部分
		I void SpL(RI k,int& x,int& y,CI f=1) {if(!k) return (void)(x=y=0);PD(k),f&&!O[k].S[0]?(x=k,SpL(SX,0)):(y=k,SpL(SY,f));}//分裂出第一个元素
		I void Bd(CI l,CI r,int& rt) {RI mid=l+r>>1;rt=New(mid?INF:0);l^mid&&(Bd(l,mid-1,O[rt].S[0]),0),r^mid&&(Bd(mid+1,r,O[rt].S[1]),0);}//初始建树,仅第0位值为0
		I void U(CI x) {T(x,1);}//子树加1
		I int Sz(CI x) {return x?(Sz(O[x].S[0])+Sz(O[x].S[1])+1):0;}//询问子树大小
	public:
		I void Init() {Bd(0,n,rt);}
		I void U(CI x,CI y) {RI a,b,c,d,k=New(x);Sp(rt,a,b,x),Sp(b,b,c,y),U(b),Mg(b,k,b),SpL(c,d,c),Mg(b,b,c),Mg(rt,a,b);}//填入[x,y]中的一个数,模拟转移
		I void GetAns() {RI a,b;Sp(rt,a,b,INF),printf("%d\n",Sz(a)-1);}
}T;
int main()
{
	RI i,x,y;for(read(n),T.Init(),i=1;i<=n;++i) read(x,y),T.U(x,y);return T.GetAns(),0;
}
posted @ 2021-11-05 20:34  TheLostWeak  阅读(99)  评论(0编辑  收藏  举报