【HDU6635 Nonsense Time】樹狀數組維護最長上升子序列

題目大意

  在一個數列ai裏依次激活某個位置,求每次激活後的最長上升子序列(LIS)。

思路

  可以倒着找。
  先找出全部數列的LIS,然後倒着令數列逐位失活,如果失活的那一位在LIS裏,就重新找一遍LIS,否則就LIS長度不變。
  現在問題就是,怎麼找LIS。
  以前都是直接dp的,這次用樹狀數組,居然也挺好用的。
  樹狀數組每個結點在變成樹狀之前,都表示以那個數字結尾的LIS。樹狀數組維護前綴最大值。
  每次重構LIS都update每一個未失活的ai。對於每次更新,都先找到它前邊的(比它小的數)的最大值,然後自己的長度就爲最大值+1,並記錄前驅。&因爲是逐位添加,所以就保證了順序。
  記得每次清空鴨~由於每次用到的地方都只會比上次更少,所以只清空這次用了的就夠!(話說這樣也相當於每次都是空的吧。。)

題解

  有極小的改動。。總覺得原來的題解有些地方有點奇怪。。

#include<cstdio>
const int N=50010;
int Case,n,i,x,a[N],b[N],ans[N],pre[N],nxt[N],f[N],g[N],used[N],bit[N]; 
inline void up(int& a, int b){
	if(f[a] < f[b]) a = b;
}
//樹狀數組求最長上升子序列 
inline void build(){
	int i,j,k;
	for(i = nxt[0]; i <= n+1; i = nxt[i]){ //i表示數字位置 
		used[i] = 0;
		k = 0;
		for(j = a[i]; j; j -= j&-j)//找到自己前邊長度最長的比自己小的 
			up(k,bit[j]);
		f[i] = f[k]+1;//當前長度爲最長的+1 
		g[i] = k;//記錄前驅 
		for(j = a[i]; j <= n+1; j += j&-j)
			up(bit[j], i);//向後更新一遍最大值 
	}
	for(i = nxt[0]; i <= n+1; i = nxt[i])
		for(j = a[i]; j <= n+1; j += j&-j)
			bit[j] = 0;
	for(i = n+1; i; i = g[i])
		used[i] = 1;
}
int main(){
	scanf("%d", &Case);
	while(Case --){
		scanf("%d", &n);
		for(i = 1; i <= n; i ++)
			scanf("%d",&a[i]), a[i] ++;
		a[n+1] = n+2;//每一個都要用長度+1 
		for(i = 0; i <= n+1; i ++)
			pre[i] = i-1, 
			nxt[i] = i+1,
			bit[i] = used[i] = 0; //bit應該是樹狀數組的本體啦 
		for(i = 1; i <= n; i ++)
			scanf("%d", &b[i]);
		build();
		for(i = n; i>=1; i --){
			ans[i] = f[n+1]-1;//總會多算結尾的n+2 
			x = b[i];
			pre[nxt[x]] = pre[x];//把x處的數字移除 
			nxt[pre[x]] = nxt[x];//pre是用來輔助nxt的 
			if(used[x]) //如果在最長上升子序列裏,就重建 
				build();
		}
		for(i = 1; i <= n; i ++)
			printf("%d%c", ans[i],i<n?' ':'\n');
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章