P4093 [HEOI2016/TJOI2016] 序列 题解

修改

  1. 偏序条件错误(9.4)。
  2. 优化原代码空间,但思路一致。

题目大意

寻找一个子序列,使得该序列在任何一个数字的一次修改或不修改后为单调不降的最大长度。

正文

显然我们可以将其看作一个带三维偏序条件的求最长不降子序列的 dp,偏序条件为:

  1. m a x i ≤ a j max _i \leq a_j maxiaj
  2. a i ≤ m i n j a_i \leq min_j aiminj
  3. i ≤ j i \leq j ij

其中 a i a_i ai 为原序列元素, m a x i max _i maxi 为修改第 i i i 项时的最大值(初始化为 a i a_i ai), m i n i min _i mini 为修改第 i i i 项时的最小值(初始化为 a i a_i ai)。

(但是本题解的重点不在此所以就不过多做解释。)

这里我使用的方法是一个神奇的写法,该写法近似于线段树套线段树但查询区间第 k k k 大时复杂度为 O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n),空间复杂度为 O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n)

该算法应该是离线算法但是被我用可持久化数组记录状态所以在线了。

不在线前代码就为三维偏序板子,只是将计数的树状数组改为求最大值的线段树。

但显然如果直接跑是会 WA 的。

我们会发现每个答案(除排序后的第一个)的求解对上几个满足该偏序条件的 dp 有依赖(也就是,如果要求 d p i dp_i dpi 必须先求出排序后的 1 ∼ i − 1 1\sim i-1 1i1 的 dp 值)。

为什么会对后面的 dp 值会产生依赖?

显然使用 cdq 完成这道题的过程为:

  1. s o l v e ( l , m i d ) solve(l,mid) solve(l,mid)
  2. 左区间对右区间的贡献。
  3. s o l v e ( m i d + 1 , r ) solve(mid+1,r) solve(mid+1,r)

而使用整体二分完成这道题的过程因为:

  1. m i d mid mid 并划分区间。
  2. 左区间对右区间的贡献。(区间划分依据是值域,且答案不会修改在操作数组上)
  3. s o l v e ( l , m i d ) solve(l,mid) solve(l,mid) s o l v e ( m i d + 1 , r ) solve(mid+1,r) solve(mid+1,r)

也就是一个是自下而上的分治。

一个是自上而下的分治。

所以我们需要在线处理。

我们可以将其理解为 N N N 次询问,每次询问第 i i i 个的 dp 值且强制在线。

如果真的执行 N N N 次整体二分,会发现有许多的区间分化、最大值是重复的,我们就可以尝试将其记录下来。

然后我们会发现,可以建 log ⁡ n \log n logn 个可持久化线段树,每层的 m i d mid mid 都先连向空树,之后每个关于 m i d mid mid 分出的区间对根节点为 r o o t m i d root_{mid} rootmid 修改, r o o t m i d root_{mid} rootmid 修改为现在的根节点(详见代码)。

查询就依据原三维偏序的题解写就好了。

有什么问题可以私信我,语文太辣鸡啦。

代码 1

#include<bits/stdc++.h>
#define N 212345
#define re register
#define inf 2147483647
using namespace std ;
int n , tr_tot[20] , root_tot[20] , q=0 , cnt=0 , maxn[N] , minn[N] , ans=1 , a[N] ;
struct tree{
	int maxn , ls , rs ;
}tr[19][200005+100000*19];
unordered_map<int,int> all_mid[20] ;
inline int made(int num,int l,int r,int floor){
	if(l==r) return num ;
	re int mid=(l+r)>>1 ;
	tr[floor][num].ls = made(++tr_tot[floor],l,mid,floor) ;
	tr[floor][num].rs = made(++tr_tot[floor],mid+1,r,floor) ;
	return num ;
}
inline int change(int last,int now,int l,int r,int floor,int k,int x){
	if(l==r){
		tr[floor][now].maxn = x ;
		return now ;
	}
	re int mid=(l+r)>>1 ;
	if(mid>=k) tr[floor][now].ls = change(tr[floor][last].ls,++tr_tot[floor],l,mid,floor,k,x) , tr[floor][now].rs = tr[floor][last].rs ;
	else tr[floor][now].rs = change(tr[floor][last].rs,++tr_tot[floor],mid+1,r,floor,k,x) , tr[floor][now].ls = tr[floor][last].ls ;
	tr[floor][now].maxn = max(tr[floor][tr[floor][now].ls].maxn,tr[floor][tr[floor][now].rs].maxn) ;
	return now ;
}
inline int find_max(int num,int l,int r,int L,int R,int floor){
	if(L>=l&&R<=r) return tr[floor][num].maxn ;
	re int mid=(L+R)>>1 , ans=0 ;
	if(mid>=l) ans = max(ans,find_max(tr[floor][num].ls,l,r,L,mid,floor)) ;
	if(mid<r) ans = max(ans,find_max(tr[floor][num].rs,l,r,mid+1,R,floor)) ;
	return ans ;
}
inline int ask(int l,int r,int x,int y,int floor,int ans){
	if(l>r) return ans ;
	re int mid=(l+r)>>1 ;
	if(l==r) return max(ans,all_mid[floor].count(mid)?find_max(all_mid[floor][mid],1,y,1,100000,floor):0);
	if(mid>=x) return ask(l,mid,x,y,floor+1,ans) ;
	else return ask(mid+1,r,x,y,floor+1,max(ans,all_mid[floor].count(mid)?find_max(all_mid[floor][mid],1,y,1,100000,floor):0)) ;
}
inline void insert(int l,int r,int x,int y,int dp,int floor){
	if(l>r) return ;
	re int mid=(l+r)>>1 ;
	if(!all_mid[floor][mid]) all_mid[floor][mid] = 1 ;
	if(mid>=x){
		re int temp=all_mid[floor][mid] ;
		re int next=all_mid[floor][mid] = ++tr_tot[floor] ;
		change(temp,next,1,100000,floor,y,dp) , (l!=r?insert(l,mid,x,y,dp,floor+1):(void)0) ;
	}else insert(mid+1,r,x,y,dp,floor+1) ;
}
inline int read(){
    int x=0 , f=1 ;
    char a=getchar() ;
    while(!(a>='0'&&a<='9')){
        if(a=='-') f = -f ;
        a = getchar() ;
    }
    while(a>='0'&&a<='9'){
        x *= 10 ;
        x += a-'0' ;
        a = getchar() ;
    }
    return x*f ;
}
signed main(){
    n = read() , q = read() ;
	for(re int i=1;i<=18;i++){
		tr_tot[i] = 1 ;
		made(1,1,100000,i) ;
	}
	for(re int i=1;i<=n;i++) a[i] = minn[i] = maxn[i] = read() ;
	for(re int i=1;i<=q;i++){
		re int x=read() , w=read() ;
		maxn[x] = max(w,maxn[x]) ;
		minn[x] = min(w,minn[x]) ;
	}
	insert(1,100000,maxn[1],a[1],1,1) ;
	for(re int i=2;i<=n;i++){
		re int temp=ask(1,100000,a[i],minn[i],1,0)+1 ;
		insert(1,100000,maxn[i],a[i],temp,1) ;
		ans = max(ans,temp) ;
	}
	cout << ans ;
	return 0 ;
}

思路一致,只是使用了平衡树节省空间。

如果需要修改原节点的贡献(也就是遇到待修的情况),只需要将原节点移到另一个平衡树上。

代码 2

#include<bits/stdc++.h>
#define N 112345
#define re register
using namespace std ;
int n , q , maxn[N] , minn[N] , cnt=0 , a[N] , ans=0 ;
unordered_map<int,int> all_mid[19] ;
struct node{
	int num , dp , down ;
	int pri ;
	int l , r ;
}t[N*19];
inline void update(int u){
	t[u].down = max(t[t[u].l].down,max(t[t[u].r].down,t[u].dp)) ;
}
inline void split(int u,int x,int &l,int &r){
	if(!u){
		l = r = 0 ;
		return ;
	}
	if(t[u].num<=x){
		l = u ;
		split(t[u].r,x,t[u].r,r) ;
	}else{
		r = u ;
		split(t[u].l,x,l,t[u].l) ;
	}
	update(u) ;
}
inline int merge(int l,int r){
	if(!l||!r) return l+r ;
	if(t[l].pri<t[r].pri){
		t[l].r = merge(t[l].r,r) ;
		update(l) ;
		return l ;
	}else{
		t[r].l = merge(l,t[r].l) ;
		update(r) ;
		return r ;
	}
}
inline int read(){
    re int x=0 ;
	re char a=getchar() ;
    while(!(a>='0'&&a<='9')){
        a = getchar() ;
    }
    while(a>='0'&&a<='9'){
        x *= 10 ;
        x += a-'0' ;
        a = getchar() ;
    }
    return x ;
}
inline int newnode(int root,int x,int dp){
	re int l , r ;
	split(root,x,l,r) ;
	++cnt ;
	t[cnt].l = t[cnt].r = 0 ;
	t[cnt].num = x ;
	t[cnt].down = t[cnt].dp = dp ;
	t[cnt].pri = rand() ;
	re int temp=merge(l,cnt) ;
	root = merge(temp,r) ;
	return root ;
}
inline void insert(int l,int r,int floor,int x,int y,int dp){
	if(l>r) return ;
	re int mid=(l+r)>>1 ;
	if(mid>=x){
		re int temp=all_mid[floor][mid] ;
		if(!temp){
			cnt++ ;
			t[cnt].l = t[cnt].r = 0 ;
			t[cnt].down = t[cnt].dp = dp ;
			t[cnt].num = y ;
			all_mid[floor][mid] = cnt ;
		}else all_mid[floor][mid] = newnode(temp,y,dp) ;
		if(l!=r) insert(l,mid,floor+1,x,y,dp) ;
	}else insert(mid+1,r,floor+1,x,y,dp) ;
}
inline int find_max(int root,int x){
	re int l , r , ans=0 ;
	split(root,x,l,r) ;
	ans = t[l].down ;
	root = merge(l,r) ;
	return ans ;
}
inline int ask(int l,int r,int floor,int x,int y,int ans){
	if(l>=r) return ans ;
	re int mid=(l+r)>>1 ;
	if(mid>=x) return ask(l,mid,floor+1,x,y,ans) ;
	else return ask(mid+1,r,floor+1,x,y,max(ans,all_mid[floor].count(mid)?find_max(all_mid[floor][mid],y):0)) ;
}
signed main(){
	srand(time(NULL)) ;
	n = read() , q = read() ;
	for(re int i=1;i<=n;i++) a[i] = minn[i] = maxn[i] = read() ;
	for(re int i=1;i<=q;i++){	
		re int x=read() , w=read() ;
		maxn[x] = w>maxn[x]?w:maxn[x] ;
		minn[x] = w<minn[x]?w:minn[x] ;
	}
	insert(1,100001,1,maxn[1],a[1],1) ;
	for(re int i=2;i<=n;i++){
		int temp=ask(1,100001,1,a[i]+1,minn[i],0)+1 ;
		insert(1,100001,1,maxn[i],a[i],temp) ;
		ans = max(ans,temp) ;
	}
	cout << ans ;
	return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值