洛谷P3928 Sequence2(dp,线段树)
题目链接:
题目大意在描述底下有。此处不赘述。
明显是个类似于LIS的dp。
令 $dp[i][j]$ 表示:
- $j=1$ 时表示已经处理了 $i$ 个数,上一个选的数来自序列 $A[0]$ 的最长长度
- $j=2$ 表示 $A[1]$
- $j=3$ 表示 $A[2]$ 且是单调递减
- $j=4$ 表示 $A[2]$ 且是单调递增
(为了方便,我们令 $seq[x]$ 表示当上文中的 $j=x$ 时表示哪个序列)
那么有转移方程:
- $dp[i][1]=\max\limits_{1\le j<i,k\in\{1,2,3,4\}}\{dp[j][k]:A[seq[k]][j]\le A[0][i]\}+1$(从前面任选一个更小的)
- $dp[i][2]=\max\limits_{1\le j<i,k\in\{1,2,3,4\}}\{dp[j][k]:A[seq[k]][j]\ge A[0][i]\}+1$(同理)
- $dp[i][3]=\max\limits_{1\le j<i,k\in\{1,2,3\}}\{dp[j][k]:A[seq[k]][j]\le A[0][i]\}+1$(因为第三个序列连续一段状态应相同,所以不能从4转移)
- $dp[i][4]=\max\limits_{1\le j<i,k\in\{1,2,4\}}\{dp[j][k]:A[seq[k]][j]\ge A[0][i]\}+1$(同上)
可以朴素dp,复杂度 $O(n^2)$。
优化?这个真的很像LIS,那么就用二分或者树状数组/线段树吧。
二分我不会……(当然二分可能做不了)
树状数组懒得打后缀最大了……
那就线段树吧,跟普通的LIS类似,开4棵线段树,分别维护 $j=1,2,3,4$。每次求一下前缀和后缀最大,然后插进线段树中即可。
(其实是可以只开3棵线段树的,$j=1,2$ 可以合在一起。但是我从前写的时候出锅了,以为是3棵线段树的锅,改成了4棵,后来发现不是这里出bug……懒得改了)
当然 $m\le 10^9$,需要离散化。
(注:离散化后最大的数可达 $3n$,所以要开4棵大小为 $3n$ 的线段树)
时间复杂度是 $O(n\log n)$。
代码+注释:(代码长得有点丑)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=100010; 4 int n,a[maxn],b[maxn],c[maxn],tmp[maxn*3],sz,ans=1; //tmp是离散化用到的数组,sz是离散化后所有数的最大值,可以算是常数优化 5 int cnt,maxv[maxn*25],ch[maxn*25][2]; //我用的动态开点线段树,一棵树节点数为约6n,而有4棵树 6 inline int lwrbnd(int x){ //离散化 7 return lower_bound(tmp+1,tmp+sz+1,x)-tmp; 8 } 9 struct segment{ //线段树维护区间最大 10 int n,root; 11 inline void pushup(int x){ 12 maxv[x]=max(maxv[ch[x][0]],maxv[ch[x][1]]); 13 } 14 void build(int &x,int l,int r){ 15 x=++cnt; 16 if(l==r) return; 17 int mid=l+r>>1; 18 build(ch[x][0],l,mid); 19 build(ch[x][1],mid+1,r); 20 } 21 segment(){} 22 segment(int n_):n(n_){build(root,1,n);} 23 void update_(int x,int l,int r,int p,int k){ 24 if(l==r) return void(maxv[x]=max(maxv[x],k)); //这里不能直接修改,如果之前相同位置的dp值更大则要保留 25 int mid=l+r>>1; 26 if(mid>=p) update_(ch[x][0],l,mid,p,k); 27 else update_(ch[x][1],mid+1,r,p,k); 28 pushup(x); 29 } 30 void update(int p,int k){ 31 update_(root,1,n,p,k); 32 } 33 int query_(int x,int L,int R,int l,int r){ 34 if(L>=l && R<=r) return maxv[x]; 35 int mid=L+R>>1,ans=0; 36 if(mid>=l) ans=max(ans,query_(ch[x][0],L,mid,l,r)); 37 if(mid<r) ans=max(ans,query_(ch[x][1],mid+1,R,l,r)); 38 return ans; 39 } 40 int query(int l,int r){ 41 return query_(root,1,n,l,r); 42 } 43 }sgm1,sgm2,sgm3,sgm4; //4棵线段树 44 int main(){ 45 scanf("%d",&n); 46 for(int i=1;i<=n;i++) scanf("%d",a+i),tmp[i]=a[i]; 47 for(int i=1;i<=n;i++) scanf("%d",b+i),tmp[i+n]=b[i]; 48 for(int i=1;i<=n;i++) scanf("%d",c+i),tmp[i+2*n]=c[i]; 49 sort(tmp+1,tmp+3*n+1); 50 sz=unique(tmp+1,tmp+3*n+1)-tmp-1; 51 sgm1=segment(sz);sgm2=segment(sz);sgm3=segment(sz);sgm4=segment(sz); //建树(我的代码这里不能连等,否则4棵线段树实际的维护值一样) 52 for(int i=1;i<=n;i++) a[i]=lwrbnd(a[i]),b[i]=lwrbnd(b[i]),c[i]=lwrbnd(c[i]); //离散化 53 for(int i=1;i<=n;i++){ 54 int tmp1=max(sgm1.query(1,a[i]),max(sgm2.query(1,a[i]),max(sgm3.query(1,a[i]),sgm4.query(1,a[i]))))+1; 55 int tmp2=max(sgm1.query(b[i],sz),max(sgm2.query(b[i],sz),max(sgm3.query(b[i],sz),sgm4.query(b[i],sz))))+1; 56 int tmp3=max(sgm1.query(1,c[i]),max(sgm2.query(1,c[i]),sgm3.query(1,c[i])))+1; 57 int tmp4=max(sgm1.query(c[i],sz),max(sgm2.query(c[i],sz),sgm4.query(c[i],sz)))+1; 58 //上面四行是从线段树中查询前缀/后缀最大值并且更新 59 ans=max(ans,max(tmp1,max(tmp2,max(tmp3,tmp4)))); 60 sgm1.update(a[i],tmp1);sgm2.update(b[i],tmp2);sgm3.update(c[i],tmp3);sgm4.update(c[i],tmp4); //插入线段树 61 } 62 printf("%d\n",ans); 63 }