CF809D Hitchhiking in the Baltic States-平衡树+DP

发布时间 2023-10-24 18:54:15作者: H_W_Y

CF809D Hitchhiking in the Baltic States-平衡树+DP

Statement

给出 \(n\) 个区间 \([l_i,r_i]\)\(n\) 个未知数 \(a_1,a_2,\dots,a_n\),现在你要确定这 \(n\) 个数,使得 \(a_i\in[l_i,r_i]\),并且这个序列的最长严格上升子序列尽可能大,求这个最大值。

\(1 \leq n\leq 3\times 10^5\)\(1 \leq l_i,r_i\leq 10^9\)

Solution

好题!很妙,非常妙!

首先感觉就很想 dp,

但是 dp 的方程式又很难推出来。。。

对于最长严格上升子序列,

我们进行套路性的操作:

\(dp[j]\) 表示最长严格上升子序列的长度为 \(j\) 时,最后一位最小的值。

很容易发现,\(dp[j]\) 一定时单调递增的。

所以我们现在考虑加入每一个元素时的转移:

令当前的 \(l_i,r_i\) 分别为 \(l,r\),每一次我们一定是从 \(dp[j-1]\) 转移到 \(dp[j]\)

  1. \(dp[j-1] \lt l\) 时,\(dp[j]=\min(dp[j],l)\)
  2. \(l \le dp[j-1] \lt r\) 时,\(dp[j]=\min(dp[j],dp[j-1]+1)\)
  3. \(dp[j-1]\ge r\) 时,\(dp[j]=dp[j]\) ,即不变。

于是我们得到了 \(O(n^2)\) 的做法。

现在考虑如何优化呢?(很难想到平衡树)

发现每一次 \(+1\) 一定是最优的,

我们现在考虑把整个数列看成一个整体,

对于操作 \(2\) ,我们可以发现其实就是相当于把区间 \([l,r-1]\) 里面的数都 \(+1\)

然后再右移一位,

而这样做完了发现有两个位置出现了冲突:

  1. \(\lt l\) 的最大的数的后一位,发现这里本来是 \(\ge l\) 的最小的数,那经过一次变化后,这一位最有的一定是 \(l\)
  2. 而对于后面冲突的 \(\ge r\) 的最小的数,它一定是不优的,直接删除。

这样的分析我们发现其实每一次操作就是在序列上面在 \(\lt l\) 的最大位置后面插入 \(l\),将 \([l,r-1]\) 区间 \(+1\) ,再删除之前就 \(\ge r\) 的最小的数,

这些操作都可以用平衡树维护。

于是我们就可以用平衡树和 DP 完成此题,

而最后的答案就是平衡树的 \(siz\)

(没有一过呜呜呜)

#include <bits/stdc++.h>
using namespace std;
mt19937 rd(time(0));

const int N=3e5+5;
int n,l,r,idx=0,rt,x,y,z;
struct Treap{
  int key,s[2],val,siz,tag;
}tr[N];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f; 
}

int nwnode(int v){
  tr[++idx].val=v;
  tr[idx].key=rd();
  tr[idx].siz=1;
  tr[idx].s[0]=tr[idx].s[1]=tr[idx].tag=0;
  return idx;
}

#define lc(p) tr[p].s[0]
#define rc(p) tr[p].s[1]
 
void pu(int p){tr[p].siz=tr[lc(p)].siz+tr[rc(p)].siz+1;}

void pd(int p){
  if(!p||!tr[p].tag) return ;
  if(lc(p)) tr[lc(p)].tag+=tr[p].tag,tr[lc(p)].val+=tr[p].tag;
  if(rc(p)) tr[rc(p)].tag+=tr[p].tag,tr[rc(p)].val+=tr[p].tag;
  tr[p].tag=0;
}

void split(int p,int k,int &x,int &y){
  if(!p){x=y=0;return;}
  pd(p);
  if(tr[p].val<=k) x=p,split(rc(p),k,rc(p),y);
  else y=p,split(lc(p),k,x,lc(p));
  pu(p);
}

int merge(int x,int y){
  if(!x||!y) return (x|y);
  pd(x);pd(y);
  if(tr[x].key<tr[y].key){
  	tr[x].s[1]=merge(tr[x].s[1],y);
  	pu(x);return x;
  }
  else{
  	tr[y].s[0]=merge(x,tr[y].s[0]);
  	pu(y);return y;
  }
}

int find(int x,int k){
  if(!k) return 0;
  while(233){
  	if(tr[lc(x)].siz>=k) x=lc(x);
  	else if(tr[lc(x)].siz+1<k) k-=tr[lc(x)].siz+1,x=rc(x);
  	else return x;
  }
}

int pre(int val){
  split(rt,val-1,x,y);
  int res=find(x,tr[x].siz);
  rt=merge(x,y);
  return res;
}

int nxt(int val){
  split(rt,val,x,y);
  int res=find(y,1);
  rt=merge(x,y);
  return res;
}

void ins(int val){
  split(rt,val,x,y);
  rt=merge(x,merge(nwnode(val),y));
}

void del(int val){
  split(rt,val,x,z);
  split(x,val-1,x,y);
  y=merge(tr[y].s[0],tr[y].s[1]);
  rt=merge(x,merge(y,z));
}

void update(int l,int r){
  split(rt,l-1,x,y);
  split(y,r,y,z);
  tr[y].tag++;tr[y].val++;
  rt=merge(merge(x,y),z);
}

int main(){
  /*2023.10.24 H_W_Y CF809D Hitchhiking in the Baltic States DP+Treap*/
  n=read();
  for(int i=1;i<=n;i++){
  	l=read();r=read();
  	if(i==1){ins(l);continue;}
  	int q=nxt(r-1);
  	update(l,r-1);
	ins(l);
  	if(q) del(tr[q].val); 
  }
  if(!rt) puts("0");
  else printf("%d\n",tr[rt].siz);
  return 0;
}

Conclusion

  1. 最长上升子序列:设 \(dp[j]\) 表示最长严格上升子序列的长度为 \(j\) 时,最后一位最小的值!!!
  2. dp 的优化可以考虑平衡树,将数列看作一个整体。