1. 程式人生 > >Codeforces 650D. Zip-line (動態LIS) (可持久化線段樹 或 離線+樹狀陣列)

Codeforces 650D. Zip-line (動態LIS) (可持久化線段樹 或 離線+樹狀陣列)

題意: 給定一個長度為n的數列,和m個詢問,每個詢問的格式是:將原陣列的第a個數改成b之後,陣列的最長上升子序列(LIS)的長度。

做法:可持久化線段樹  或  離線+樹狀陣列

令 h[ ] 為原陣列,LIS_L[ i ] 表示以 i 結尾的LIS長度,LIS_R[ i ] 表示以 i 開頭的LIS長度。這倆陣列用普通LIS演算法O(nlog2(n))求出。

然後來分析一下詢問怎麼處理:

在 h[a]=b之後,整個數列的LIS分兩部分,一是包含b的LIS和不包含b的LIS。

包含b的LIS: 

1. 在 i < a 且 h[ i ] < b 的所有LIS_L[ i ]中求最大值,這是可以接在b之前的最大LIS長度。

2. 在 i > a 且 h[ i ] > b 的所有LIS_R[ i ]中求最大值,這是可以接在b之後的最大LIS長度。

所以包含b的LIS長度就是上面兩個長度之和再加上一。

不包含b的LIS:

假設原陣列的LIS長度為k,挖掉下標為a的這個數之後,有兩種情況。

1. 所有的LIS都包括h[a](這裡是指原陣列的h[a]),這時新LIS長度為 k - 1.

2.至少有一個LIS不包括h[a],這時LIS長度不受影響,仍為k。

對上面兩個值求最大值,就是詢問的答案。

所以問題就只剩下了兩個:

問題一:對於包含b的LIS,如何快速求出那兩個值。

問題二:對於不包含b的LIS,怎麼判斷是否所有的LIS都經過h[a]。

問題一: 可持久化線段樹      或者    離線+樹狀陣列 

要求的值:在 i < a 且 h[ i ] < b 的所有LIS_L[ i ]中求最大值。

可持久化線段樹:

注意到這裡有兩個限制條件,h[ i ] < b 這個條件,可以將所有數值離散化,然後將所有LIS_L的值都加入進去,

不過,每個LIS_L[ i ]加入的橫座標是h[i]對應的排名,這樣通過線段樹的區間查詢,就可以找到h[ i ] < b 的所有LIS_L值。

問題在於第一個條件,要求 i < a ,也就是說,隨便給一個i,要知道前 i 個數形成的上述線段樹,明顯用可持久化線段樹。

直接建n棵樹,對每個 i 值建立前 i 個值形成的線段樹。空間複雜度O(nlog2(n)),時間複雜度O(nlog2(n))。

問題解決,R那邊對稱著寫一寫就好了。

離線+樹狀陣列:

上面的解法中,h[ i ] < b這條件是靠線段樹區間詢問來解決的,而 i < a 這個條件是利用了可持久化資料結構。

另一種解決 i < a 這個條件的做法就是將詢問離線,也就是說,在每個 i 上,記錄有多少個詢問需要前 i 個LIS_L組成的線段樹。

在依次將元素加入線段樹的同時,遇到可以計算的詢問,就計算,這樣的話,只需要普通的線段樹來維護最大值就行了。

然後維護最大值用樹狀陣列會更快,所以就直接離線+樹狀陣列解決。

從左到右掃描一遍,再從右往左掃描一遍,就計算完畢。

問題二:

首先一個結論:如果一個數h[a]存在於多個LIS中,那麼它在多個LIS中的排名是一樣的,比如它在某個LIS中是第二個數,那麼它在所有LIS中都是第二個數。

用陣列pos[ i ] 來存每個數在LIS中的位置,0表示不在LIS中。

如果LIS_L[ i ] + LIS_R[ i ] = k + 1 那麼,這個數就在LIS中,並且位置是 LIS_L[ i ]。

否則,不在LIS中。

然後用Count陣列,對所有的位置進行統計,Count[ j ] 表示有多少個數可以擔任位置 j 。

這樣處理完之後,對於i來說,只要 Count[ pos[ i ] ] 等於 1,就表示所有LIS都經過這個數,所以拿走之後LIS長度為 k - 1.

否則,LIS長度為 k 。

兩個問題都解決了,這題也就做出來了。

終於又碰到一題可以用可持久化線段樹了,真是不容易。雖然這題每段程式碼都是左寫一遍右寫一遍,但是還是寫的很爽。

陣列開小了一點點WA了好幾次,只小了一點點,所以越界了一點點但是沒有訪問到非法記憶體,所以仍然報的是WA而不是RE。

可持久化線段樹(1933ms  201968KB):

/* 1933 ms	201968 KB  */ 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define out(i) <<#i<<"="<<(i)<<"  "
#define OUT1(a1) cout out(a1) <<endl
#define OUT2(a1,a2) cout out(a1) out(a2) <<endl
#define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl
#define maxn 400007
using namespace std;
//離散化 
int Rank[maxn],Rn;
void SetRank(int n){
	sort(Rank+1,Rank+n+1);
	Rn = 1;
	for(int i=2;i<=n;++i)
		if(Rank[i]^Rank[i-1])
			Rank[++Rn]=Rank[i];
}
int GetRank(int x){
	int L=1,R=Rn,M;//[L,R] first >= x
	while(L^R){
		M = (L+R)>>1;
		if(Rank[M] < x) L = M + 1;
		else R = M;
	}
	return L;
}
int GetRank_R(int x){
	int L=1,R=Rn,M;//[L,R] first > x
	while(L^R){
		M = (L+R)>>1;
		if(Rank[M] <= x) L = M + 1;
		else R = M;
	}
	return L;
}
//題目資料 
int n,m,a,b,k;
int h[maxn];

//LIS部分
int LIS_L[maxn],LIS_R[maxn],Len[maxn],Ln;
int Test_L(int v){
	int L=0,R=Ln+1,M;//[L,R)  last < v
	while(L + 1 < R){
		M = (L + R) >> 1;
		if(Len[M] < v) L = M;
		else R = M;
	}
	return L;
}
int Test_R(int v){
	int L=0,R=Ln+1,M;//[L,R) last > v
	while(L + 1 < R){
		M = (L + R) >> 1;
		if(Len[M] > v) L = M;
		else R = M;
	}
	return L;
}
void LIS(){
	Len[0]=Ln=0;
	for(int i=1;i<=n;++i){
		LIS_L[i] = Test_L(h[i]) + 1;
		if(Ln < LIS_L[i]) Len[++Ln] = h[i];
		else Len[LIS_L[i]]=min(Len[LIS_L[i]],h[i]);
	}
	k = Ln;//記錄最長LIS 
	Len[0]=1000000001;Ln=0;
	for(int i=n;i>=1;--i){
		LIS_R[i] = Test_R(h[i]) + 1;
		if(Ln < LIS_R[i]) Len[++Ln] = h[i];
		else Len[LIS_R[i]]=max(Len[LIS_R[i]],h[i]);
	}
}
//主席樹部分
int LPS_L[maxn*20],LPS_R[maxn*20],LPS_V[maxn*20],LPS_T[maxn],LPS_TP;
void LPS_Add(int &rt,int l,int r,int x,int v){
	++LPS_TP;
	LPS_L[LPS_TP]=LPS_L[rt];
	LPS_R[LPS_TP]=LPS_R[rt];
	LPS_V[LPS_TP]=max(LPS_V[rt],v);
	rt = LPS_TP;
	if(l==r) return;
	int m = (l + r) >> 1;
	if(x <= m) LPS_Add(LPS_L[rt],l,m,x,v);
	else       LPS_Add(LPS_R[rt],m+1,r,x,v);
}
void LPS_Build(){
	LPS_L[0]=LPS_R[0]=LPS_V[0]=LPS_T[0]=LPS_TP=0;
	for(int i=1;i<=n;++i){
		LPS_Add(LPS_T[i]=LPS_T[i-1],1,Rn,GetRank(h[i]),LIS_L[i]);
	}
}
int LPS_Query(int rt,int l,int r,int x){
	if(l == r) return LPS_V[rt];
	int m = (l + r) >> 1;
	if(x <= m) return LPS_Query(LPS_L[rt],l,m,x);
	else return max(LPS_V[LPS_L[rt]],LPS_Query(LPS_R[rt],m+1,r,x));
}
int RPS_L[maxn*20],RPS_R[maxn*20],RPS_V[maxn*20],RPS_T[maxn],RPS_TP;
void RPS_Add(int &rt,int l,int r,int x,int v){
	++RPS_TP;
	RPS_L[RPS_TP]=RPS_L[rt];
	RPS_R[RPS_TP]=RPS_R[rt];
	RPS_V[RPS_TP]=max(RPS_V[rt],v);
	rt = RPS_TP;
	if(l==r) return;
	int m = (l + r) >> 1;
	if(x <= m) RPS_Add(RPS_L[rt],l,m,x,v);
	else       RPS_Add(RPS_R[rt],m+1,r,x,v);
}
void RPS_Build(){
	RPS_L[n+1]=RPS_R[n+1]=RPS_V[n+1]=RPS_T[n+1]=RPS_TP=0;
	for(int i=n;i>=1;--i){
		RPS_Add(RPS_T[i]=RPS_T[i+1],1,Rn,GetRank(h[i]),LIS_R[i]);
	}
}
int RPS_Query(int rt,int l,int r,int x){
	if(l == r) return RPS_V[rt];
	int m = (l + r) >> 1;
	if(x <= m) return max(RPS_Query(RPS_L[rt],l,m,x),RPS_V[RPS_R[rt]]); 
	else return RPS_Query(RPS_R[rt],m+1,r,x);
}
int Count[maxn];//每種位置的數的數量
int pos[maxn];//第i個數在LIS中的位置,0表示不在LIS中 
int main(void)
{
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=n;++i) scanf("%d",&h[i]),Rank[i]=h[i];
		//離散化
		Rank[n+1]=0;Rank[n+2]=1000000001;
		SetRank(n+2);
		//建立LIS陣列 
		LIS();
		//建主席樹 
		LPS_Build();
		RPS_Build();
		memset(Count,0,sizeof(Count));
		for(int i=1;i<=n;++i){
			int L = LIS_L[i],R = LIS_R[i]; 
			if(L+R==k+1){
				Count[pos[i]=L]++;
			}
			else pos[i]=0;
		} 
		for(int i=1;i<=m;++i){
			scanf("%d%d",&a,&b);
			int _L = a - 1, _R = a + 1;
			int ANS1=1+LPS_Query(LPS_T[_L],1,Rn,GetRank(b)-1)+RPS_Query(RPS_T[_R],1,Rn,GetRank_R(b));
			int ANS2=Count[pos[a]]^1?k:k-1;
			printf("%d\n",max(ANS1,ANS2));
		}
	}
return 0;
}





離線+樹狀陣列(873 ms39200 KB):

/*  873 ms<span style="white-space:pre">	</span>39200 KB */ 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <list>
#define out(i) <<#i<<"="<<(i)<<"  "
#define OUT1(a1) cout out(a1) <<endl
#define OUT2(a1,a2) cout out(a1) out(a2) <<endl
#define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl
#define maxn 400007
using namespace std;
struct Node{
	int id,v;
	Node(){}
	Node(int id,int v):id(id),v(v){}
};
list<Node> List[maxn];
//離散化 
int Rank[maxn<<1],Rn;
void SetRank(int n){
	sort(Rank+1,Rank+n+1);
	Rn = 1;
	for(int i=2;i<=n;++i)
		if(Rank[i]^Rank[i-1])
			Rank[++Rn]=Rank[i];
}
int GetRank(int x){
	int L=1,R=Rn,M;//[L,R] first >= x
	while(L^R){
		M = (L+R)>>1;
		if(Rank[M] < x) L = M + 1;
		else R = M;
	}
	return L;
}
//題目資料 
int n,m,a[maxn],b[maxn],k;
int h[maxn];

//LIS部分
int LIS_L[maxn],LIS_R[maxn],Len[maxn],Ln;
int Test_L(int v){
	int L=0,R=Ln+1,M;//[L,R)  last < v
	while(L + 1 < R){
		M = (L + R) >> 1;
		if(Len[M] < v) L = M;
		else R = M;
	}
	return L;
}
int Test_R(int v){
	int L=0,R=Ln+1,M;//[L,R) last > v
	while(L + 1 < R){
		M = (L + R) >> 1;
		if(Len[M] > v) L = M;
		else R = M;
	}
	return L;
}
void LIS(){
	Len[0]=Ln=0;
	for(int i=1;i<=n;++i){
		LIS_L[i] = Test_L(h[i]) + 1;
		if(Ln < LIS_L[i]) Len[++Ln] = h[i];
		else Len[LIS_L[i]]=min(Len[LIS_L[i]],h[i]);
	}
	k = Ln;//記錄最長LIS 
	Len[0]=1000000001;Ln=0;
	for(int i=n;i>=1;--i){
		LIS_R[i] = Test_R(h[i]) + 1;
		if(Ln < LIS_R[i]) Len[++Ln] = h[i];
		else Len[LIS_R[i]]=max(Len[LIS_R[i]],h[i]);
	}
}
//樹狀陣列
int BIT_L[maxn<<1],BIT_R[maxn<<1];
void Add_L(int x,int v){
	while(x <= Rn){
		BIT_L[x]=max(BIT_L[x],v);
		x+=x&-x;
	}
}
int Query_L(int x){
	int ANS = 0;
	while(x > 0){
		ANS = max(ANS,BIT_L[x]);
		x-=x&-x;
	}
	return ANS;
}
void Add_R(int x,int v){
	while(x > 0){
		BIT_R[x]=max(BIT_R[x],v);
		x-=x&-x;
	}
}
int Query_R(int x){
	int ANS = 0;
	while(x <= Rn){
		ANS = max(ANS,BIT_R[x]);
		x+=x&-x;
	}
	return ANS;
}
int Count[maxn];//每種位置的數的數量
int pos[maxn];//第i個數在LIS中的位置,0表示不在LIS中 
int L[maxn],R[maxn];//記錄左右的答案 
int main(void)
{
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=n;++i) scanf("%d",&h[i]),Rank[i]=h[i],List[i].clear();
		//讀取詢問
		for(int i=1;i<=m;++i){
			scanf("%d%d",&a[i],&b[i]);
			List[a[i]].push_back(Node(i,b[i]));
			Rank[n+i]=b[i];
		}
		//離散化
		Rank[n+m+1]=0;Rank[n+m+2]=1000000001;
		SetRank(n+m+2);
		
		//建立LIS陣列 
		LIS();
		//計算Count和pos 
		memset(Count,0,sizeof(Count));
		for(int i=1;i<=n;++i){
			int L = LIS_L[i],R = LIS_R[i]; 
			if(L+R==k+1){
				++Count[pos[i]=L];
			}
			else pos[i]=0;
		}
		//開始計算
		memset(BIT_L,0,sizeof(BIT_L));
		for(int i=1;i<=n;++i){
			for(list<Node>::iterator it=List[i].begin();it!=List[i].end();++it){
				L[it->id]=Query_L(GetRank(it->v)-1);
			}
			Add_L(GetRank(h[i]),LIS_L[i]);
		}
		memset(BIT_R,0,sizeof(BIT_R));
		for(int i=n;i>=1;--i){
			for(list<Node>::iterator it=List[i].begin();it!=List[i].end();++it){
				R[it->id]=Query_R(GetRank(it->v)+1);
			}
			Add_R(GetRank(h[i]),LIS_R[i]);
		}
		for(int i=1;i<=m;++i){
			int ANS1 = 1 + L[i]+R[i];
			int ANS2 = Count[pos[a[i]]]!=1?k:k-1;
			printf("%d\n",max(ANS1,ANS2));
		}
	}
return 0;
}