BZOJ3173(平衡树+最长上升子序列)
哇!这道题花了我一下午加一晚上,主要还是因为没有理解的很透彻就开始敲代码的缘故吧!现在要清清楚楚地分析一遍。
题意的重点还是要说一下,第i次插入i,但插入的位置不一定是按顺序的,题给的就是每个数插入的位置,求的是每插入一个数就回答当前最长上升子序列长度
比如样例:
3
0 0 2
就是一个
1
2 1
2 1 3
的过程。
注意:真正插入的位置是输入的x再加1
分析得知,每次插入一个新数x,都一定是当前序列中的最大值。那么对于当前序列的最长上升子序列,要么是以x结尾的最长上升子序列,要么是在前面就有一个更长的序列(以其他数为结尾的最长上升子序列)。
而且,输出的答案一定是单调不降的
那我们有这样的一个做法:
对于每一个数x,都把它插入平衡树中,同时维护平衡树中目前序列的最长上升子序列的长度maxn(可能并不是以x结尾)。
根据平衡树性质:x旋到root后,ch[x][0]即为当前序列x前面的数,答案也自然是从前面的序列更新而来。
重要的是update的时候要维护maxn
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void update(int x) { siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1; maxn[x]=g[x]; maxn[x]=max(maxn[x],maxn[ch[x][0]]); maxn[x]=max(maxn[x],maxn[ch[x][1]]); }
还有一点要注意的是:
平衡树里插入的是位置
插入时,假如要求插入到第k个位置,应该要先找到第k大再插入,而不是直接插入,因为输入的x可能是一样的,但序列早就变了。
找到第x大(因为要插入的位置是x+1),把它旋转到root,然后把第x+1大(后面称为m)旋到root的右儿子,再把要插入的数旋到m的左儿子。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define INF 2100000000 #define N 100003 using namespace std; int read() { int f=1,x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } int root=-INF; int ans=-INF,n; int num=0,tot=0; int f[N],ch[N][4],siz[N],key[N],maxn[N],g[N]; int get(int x) { return ch[f[x]][1]==x; } void update(int x) { siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1; maxn[x]=g[x]; maxn[x]=max(maxn[x],maxn[ch[x][0]]); maxn[x]=max(maxn[x],maxn[ch[x][1]]); } void rotate(int x) { int old=f[x],oldf=f[old]; int which=get(x); ch[old][which]=ch[x][which^1];f[ch[old][which]]=old; f[old]=x;ch[x][which^1]=old; f[x]=oldf; if(oldf) ch[oldf][ch[oldf][1]==old]=x; update(old);update(x); } void splay(int x,int tar) { for (int fa;(fa=f[x])!=tar;rotate(x)) if (f[fa]!=tar) rotate(get(x)==get(fa)?fa:x); if (!tar) root=x; } int find(int x){ int now=root; while (1){ if (ch[now][0]&&x<=siz[ch[now][0]]) now=ch[now][0]; else{ int temp=siz[ch[now][0]]+1; if (x==temp) return now; x-=temp;now=ch[now][1]; } } } int main() { n=read(); root=++num; key[num]=1000000000; siz[root]=1; f[root]=0; ch[root][1]=++num; key[num]=0; siz[num]=1; ch[num][1]=ch[num][0]=0; f[num]=root; for(int i=1;i<=n;++i) { int x=read();x++; int m=find(x); int n=find(x+1); splay(m,0); splay(n,m); int t=ch[root][1]; ch[t][0]=++num;f[num]=t;key[num]=i;ch[num][1]=ch[num][0]=0;siz[num]=1; splay(t,0); splay(num,0); g[num]=maxn[ch[root][0]]+1; update(num); printf("%d\n",maxn[num]); } }
还有就是常常要splay一下保证复杂度
还有一种做法是先处理完最终的序列,再用树状数组维护最长上升子序列(看日后什么时候去码一下)。