UVA1471 防线 Defense Lines

题目大意

%   给定一个长度为 n n n 的序列 A A A,定义一个序列的连续上升子序列为这个序列中数值严格递增的一段连续子序列,你需要删除其中连续的一段,使得剩下的元素构成的连续上升子序列尽可能长,你需要输出这个最长的长度。
%   数据范围  n ∈ ( 0 , 2 ⋅ 1 0 5 ] ∩ N , a i ∈ ( 0 , 1 0 9 ] ∩ N ∗ n\in(0, 2\cdot10^5]∩\N,a_i\in(0,10^9]∩\N^* n(0,2105]N,ai(0,109]N

题解

%   考虑最朴素的算法,先枚举删掉的一段的起始位置 i i i,结束位置 j j j,然后计算出最长的连续上升子序列,总时间复杂度为 Θ ( n 3 ) \Theta(n^3) Θ(n3)
  考虑优化每次计算最长的连续上升子序列的过程,可以发现,如果能通过删除区间 [ i , j ] [i,j] [i,j] 得到更长的最长的连续上升子序列,必然有 a i < a j a_i<a_j ai<aj,因而我们可以预处理出从每个位置开始向前的最长连续上升子序列 p i p_i pi 和向后的连续上升子序列 f i f_i fi。令 P = max ⁡ i = 1 n p i , F = max ⁡ i = 1 n f i P=\max_{i=1}^n p_i,F=\max_{i=1}^n f_i P=maxi=1npi,F=maxi=1nfi,则删除 [ i , j ] [i,j] [i,j] 后的答案不会大于 max ⁡ ( p r e i + s u f j , P , F ) \max(pre_i+suf_j,P,F) max(prei+sufj,P,F)。如此,程序的时间复杂度被优化到了 Θ ( n 2 ) \Theta(n^2) Θ(n2)
  这样的时间复杂度仍然无法通过本题,我们考虑能否只枚举 i i i。对于每个 i i i ,我们的目标是找到一个 j j j,满足 a j > a i a_j>a_i aj>ai,且 s u f j suf_j sufj 尽可能大。
  如果我们用 t s t_s ts 表示所有满足 a k = s a_k=s ak=s k k k 对应的最大的 s u f k suf_k sufk,则我们可以从后往前枚举 i i i,每次统计满足 s ∈ [ a i , + ∞ ] s\in [a_i,+\infty] s[ai,+] 的最大的 t s t_s ts。可以发现,如果用线段树来维护 t t t,每次单点修改,区间查询最大值,就可以做到 Θ ( n log ⁡ 2 n ) \Theta(n\log_2 n) Θ(nlog2n) 的时间复杂度。
  考虑到 a i a_i ai 的范围过大,因而需要离散化。

代码

#include<bits/stdc++.h>
using namespace std;
#define maxn 200010
int T,n,a[maxn],pre[maxn],fix[maxn],t[maxn];
struct node{
	int l,r,maxx;
	node *left,*right;
	node(int tl=0,int tr=0):l(tl),r(tr),left(NULL),right(NULL),maxx(0){
		if(tl==tr) return;
		int mid=(tl+tr)>>1;
		left=new node(tl,mid);
		right=new node(mid+1,tr);
	}
	void change(int x,int d){
		if(l==r) return (void)(maxx=d);
		if(x<=left->r) left->change(x,d);
		else right->change(x,d);
		maxx=max(left->maxx,right->maxx);
	}
	int ask(int tl,int tr){
		if(tl>tr) return 0;
		if(l==tl&&r==tr) return maxx;
		if(tr<=left->r) return left->ask(tl,tr);
		else if(tl>=right->l) return right->ask(tl,tr);
		return max(left->ask(tl,left->r),right->ask(right->l,tr));
	}
};
int arr[maxn];
int main(){
	scanf("%d",&T);
	while(T--){
		memset(arr,0,sizeof arr);
		memset(pre,0,sizeof pre);
		memset(fix,0,sizeof fix);
		int ans=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",a+i);
			t[i]=a[i];
		} sort(t+1,t+1+n);
		int m=unique(t+1,t+1+n)-(t+1);
		for(int i=1;i<=n;i++)
			a[i]=lower_bound(t+1,t+1+m,a[i])-t;
		pre[1]=1;
		for(int i=2;i<=n;i++){
			if(a[i]>a[i-1]) pre[i]=pre[i-1]+1;
			else pre[i]=1;
			ans=max(ans,pre[i]);
		}
		fix[n]=1;
		for(int i=n-1;i>=1;i--){
			if(a[i]<a[i+1]) fix[i]=fix[i+1]+1;
			else fix[i]=1;
			ans=max(ans,fix[i]);
		} node T(1,m);
		for(int i=n;i>=1;i--){
			ans=max(ans,pre[i]+T.ask(a[i]+1,m));
			if(arr[a[i]]<fix[i]){
				arr[a[i]]=fix[i];
				T.change(a[i],fix[i]);
			}
		} printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值