Codeforces 542A. Place Your Ad Here (掃描線進階 帶權值的線段交求最大值) (線段樹)

題目:

給n個線段:[ L , R]     

給m個線段:[ A , B ] 權值爲C

(1<= n , m <= 200000)   (0<=L<=R<=10^9)  (0<=A<=B<=10^9)  (1<=C<=10^9)

要求:在兩組線段中各選一個,使得兩個線段的相交部分[ X,Y] 滿足 (Y-X)*C 的值最大,求這個最大值以及選擇的是哪一對線段。


一類線段帶權值,一類不帶權值,所以,對於每個帶權值的線段,找出跟它相交長度最大的線段,遍歷就行了。

直接做是O(mn)會超時,所以需要用到掃描線,這個掃描線還是比較複雜的。

首先,線段相交分爲以下四種情況:




將所有線段都加入掃描線,每條線段都出現兩次:一次入邊, 一次出邊。

以下就用L,R代表無權值線段的入邊,出邊。用A,B代表有權值線段的入邊,出邊。

掃描線的過程,就是將所有的A,B,L,R排序,然後從小到大掃描一遍。

並且,當座標一樣時,先進行L,R操作(更新數據結構記錄的信息),再進行A,B操作(A,B的操作其實就是更新最終答案)。

那麼,整個過程實際上,就是在A,B,L,R這四個端點各需要進行什麼操作的問題。


以下,設當前座標爲X,按座標從小到大掃描。L,R,A,B中未列出的操作表示不操作。

情況一: 維護覆蓋了X點的LR線段中,可以達到的最大R值是多少 O(n)

L:更新最大的R值

A:若最大R值大於等於B,更新答案。

這種方法實際上,可以計算所有覆蓋了A端點的答案。

情況二: O(n log2(n))

R:將R-L這個值加入L的位置。O(log2(n))

B:在區間[A,B]中查找最大值。O(log2(n))

這樣做可以找出所有AB覆蓋LR的線段,是因爲碰到了R端點,纔會將R-L加入L位置。

所以,碰到B端點時,在區間[A,B]中,搜到的所有的線段,都是L>=A 並且 R<=B的LR線段。

情況三:

可以跟情況一用同一種方法來做,情況四需要反向掃描。

另一種辦法是用平衡樹:O(n log2(n))

L:將本線段R值加入平衡樹

R:將R值從平衡樹中刪除

A:找到平衡樹中的最大值,計算。

在A點的平衡樹的線段LR中,L<=A 並且 R > A

情況四:

L:將L值加入平衡樹

R:將L值從平衡樹中刪除

B:找到平衡樹中最小值,計算。

在B點的平衡樹的線段LR中,L<=B 並且 R > B


在代碼中,將情況一跟三合併成了覆蓋了A端點的答案。

所以代碼中分了三類:

一:AB包含LR的情況      L>=A    R<=B          --  用線段樹維護,單次操作O(log2(n))  總複雜度 O(n log2(n))

二:LR覆蓋了A點的情況  L<=A   R>A           --單次操作:平衡樹O(log2(n))   或情況一中描述的方法O(1)

三:LR覆蓋了B點的情況  L<=B   R>B            --同第二類。


第一類就用線段樹O(n*log2(n))

如果二,三類,使用情況一描述的O(n)方法,第二類需要從左往右掃描,第三類需要從右往左掃描,但是都是O(n)複雜度。

如果二,三類,使用平衡樹,則只需要從左往右掃描一次,但是總複雜度是O(n*log2(n))


實測:平衡樹是982ms  另一種方法是561ms。


平衡樹代碼:(982ms   49300KB)

/*
982 ms	49300 KB
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring> 
#define maxn 200007
#define LL long long
using namespace std;
//SBT(Size Balanced Tree)
struct Node{
	int V,ID;
	Node():V(0),ID(0){}
	Node(int V,int ID):V(V),ID(ID){}
	bool operator <(const Node &B)const{return V < B.V || V==B.V && ID < B.ID;}
	bool operator ==(const Node &B)const{return V==B.V&&ID==B.ID;}
}K[maxn<<1];
int L[maxn<<1],R[maxn<<1],S[maxn<<1],IP,TA,TB;
void zig(int &x){int t=R[x];R[x]=L[t];L[t]=x;S[t]=S[x];S[x]=S[L[x]]+S[R[x]]+1;x=t;} 
void zag(int &x){int t=L[x];L[x]=R[t];R[t]=x;S[t]=S[x];S[x]=S[L[x]]+S[R[x]]+1;x=t;}
void lev(int &x,bool flag){
	if(flag){
		if(S[L[L[x]]]>S[R[x]]) zag(x);
		else if(S[R[L[x]]]>S[R[x]]) {zig(L[x]);zag(x);}
		else return;
	}
	else{
		if(S[R[R[x]]]>S[L[x]]) zig(x);
		else if(S[L[R[x]]]>S[L[x]]){zag(R[x]);zig(x);}
		else return;
	}
	lev(L[x],true);lev(R[x],false);
	lev(x,true);lev(x,false);
}
void Insert(int &rt,Node X){
	if(!rt) {rt=++IP;L[rt]=R[rt]=0;S[rt]=1;K[rt]=X;return;}
	X < K[rt]?Insert(L[rt],X):Insert(R[rt],X);
	++S[rt];lev(rt,X < K[rt]);
}
Node Delete(int &rt,Node X){
	Node Del;--S[rt];
	if(X == K[rt] || X < K[rt] && !L[rt] || K[rt] < X && !R[rt]){
		Del=K[rt];
		if(!L[rt]||!R[rt]) rt=L[rt]+R[rt];
		else K[rt]=Delete(L[rt],X);
	}
	else Del=X < K[rt]?Delete(L[rt],X):Delete(R[rt],X);
	lev(rt,K[rt]<X);
	return Del;
}
int Min(int &rt){return L[rt]?Min(L[rt]):rt;}
int Max(int &rt){return R[rt]?Max(R[rt]):rt;}
//非遞歸線段樹 -- 單點修改,維護區間最大值 
//Segment Tree 
Node ST[maxn<<4]; 
int N;
void Build(int n){//初始化
	N=1;while(N < n+2) N <<= 1;
	int I=N <<1;
	Node X=Node(0,0);
	for(int i=0;i<I;++i) ST[i]=X;
}
void Update(int L,Node X){//點更新
	for(int s=N+L;s;s>>=1){
		if(ST[s]<X) ST[s]=X;
		else break;
	}
}
Node Query(int L,int R){//查詢區間最大值
	Node ANS;
	for(int s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1){
		if(~s&1&&ANS < ST[s^1]) ANS=ST[s^1];
		if( t&1&&ANS < ST[t^1]) ANS=ST[t^1];
	}
	return ANS;
}
//離散化
int Rank[maxn<<2],Rn;
void SortRank(){
	int I=1;
	sort(Rank+1,Rank+Rn+1);
	for(int i=2;i<=Rn;++i) if(Rank[i]!=Rank[i-1]) Rank[++I]=Rank[i];
	Rn=I;
}
int GetRank(int x){
	int L=1,R=Rn;//[L,R] first >=x
	while(L^R){
		int M=(L+R)>>1;
		if(Rank[M]<x) L=M+1;
		else R=M; 
	}
	return L;
}
//Line Information
struct Window{
	int A,B,C;//[A,B)--C
	Window(){}
	Window(int A,int B,int C):A(A),B(B),C(C){}
}W[maxn];int Wn;
struct Ad{
	int L,R;//[L,R)
	Ad(){}
	Ad(int L,int R):L(L),R(R){}
}A[maxn];int An;
//SweepLine
struct SweepLine{
	int X,ID;
	bool TYPE,IN;//type1=Ad type0=Window 
	SweepLine(){}
	SweepLine(int X,int ID,int TYPE,int IN):X(X),ID(ID),TYPE(TYPE),IN(IN){}
	bool operator <(const SweepLine &B)const{return X < B.X || X==B.X && TYPE < B.TYPE;} 
}Line[maxn<<2];int Ln;
int n,m;
int main(void)
{
	while(~scanf("%d%d",&n,&m)){
		Ln=Rn=An=Wn=0;
		for(int i=1;i<=n;++i){
			int l,r;scanf("%d%d",&l,&r);
			Rank[++Rn]=l;
			Rank[++Rn]=r;
			A[++An]=Ad(l,r);
		}
		for(int i=1;i<=m;++i){
			int a,b,c;scanf("%d%d%d",&a,&b,&c);
			Rank[++Rn]=a;
			Rank[++Rn]=b;
			W[++Wn]=Window(a,b,c);
		}
		//離散化
		SortRank();
		//掃描線
		for(int i=1;i<=n;++i){
			Line[Ln++]=SweepLine(GetRank(A[i].L),i,1,1);
			Line[Ln++]=SweepLine(GetRank(A[i].R),i,1,0);
		}
		for(int i=1;i<=m;++i){
			Line[Ln++]=SweepLine(GetRank(W[i].A),i,0,1);
			Line[Ln++]=SweepLine(GetRank(W[i].B),i,0,0);
		}
		sort(Line,Line+Ln);
		L[0]=R[0]=S[0]=IP=TA=TB=0;//SBT初始化 
		Build(Rn);//線段樹初始化 
		LL ANS=0;int I=0,AID,WID;
		for(int i=1;i<=Rn;++i){
			while(I < Ln && Line[I].X == i){
			 	if(Line[I].TYPE){//更新 
			 		if(Line[I].IN){//L操作 
			 			Insert(TB,Node(A[Line[I].ID].L,Line[I].ID));
			 			Insert(TA,Node(A[Line[I].ID].R,Line[I].ID));
			 		}
			 		else{//R操作 
			 			Delete(TB,Node(A[Line[I].ID].L,Line[I].ID));
			 			Delete(TA,Node(A[Line[I].ID].R,Line[I].ID));
			 			Update(GetRank(A[Line[I].ID].L),Node(A[Line[I].ID].R-A[Line[I].ID].L,Line[I].ID));
			 		}
			 	}
			 	else{//計算 
			 		if(Line[I].IN){//A操作 
			 			int rt=Max(TA);
			 			if(rt){
				 			LL D=((LL) W[Line[I].ID].C)*(min(W[Line[I].ID].B,K[rt].V)-W[Line[I].ID].A);
				 			if(D > ANS){
				 				ANS=D;
				 				WID=Line[I].ID;
				 				AID=K[rt].ID;
				 			}	
			 			}
			 			
			 		}
			 		else{//B操作 
			 			int rt=Min(TB);
			 			if(rt){
				 			LL D=((LL) W[Line[I].ID].C)*(W[Line[I].ID].B-max(W[Line[I].ID].A,K[rt].V));
				 			if(D > ANS){
				 				ANS=D;
				 				WID=Line[I].ID;
				 				AID=K[rt].ID;
				 			}	
			 			}
			 			
			 			Node X=Query(GetRank(W[Line[I].ID].A),GetRank(W[Line[I].ID].B));
			 			if(X.V){
				 			LL D=((LL) W[Line[I].ID].C)*X.V;
				 			if(D > ANS){
				 				ANS=D;
				 				WID=Line[I].ID;
				 				AID=X.ID;
				 			}
			 			}
			 		}
			 	}
			 	++I;
			}
		}
		printf("%I64d\n",ANS);
		if(ANS) printf("%d %d\n",AID,WID);
	}
return 0;
}

另一份代碼:(561ms   41500KB)

/*
561 ms	41500 KB
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring> 
#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 200007
#define LL long long
using namespace std;
//非遞歸線段樹 -- 單點修改,維護區間最大值 
//Segment Tree 
struct Node{
	int V,ID;
	Node():V(0),ID(0){}
	Node(int V,int ID):V(V),ID(ID){}
	bool operator <(const Node &B)const{return V < B.V || V==B.V && ID < B.ID;}
	bool operator ==(const Node &B)const{return V==B.V&&ID==B.ID;}
}ST[maxn<<4];
int N;
void Build(int n){
	N=1;while(N < n+2) N <<= 1;
	int I=N <<1;
	Node X=Node(0,0);
	for(int i=0;i<I;++i) ST[i]=X;
}
void Update(int L,Node X){//點更新
	for(int s=N+L;s;s>>=1){
		if(ST[s]<X) ST[s]=X;
		else break;
	}
}
Node Query(int L,int R){//查詢區間最大值
	Node ANS;
	for(int s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1){
		if(~s&1&&ANS < ST[s^1]) ANS=ST[s^1];
		if( t&1&&ANS < ST[t^1]) ANS=ST[t^1];
	}
	return ANS;
}
//離散化
int Rank[maxn<<2],Rn;
void SortRank(){
	int I=1;
	sort(Rank+1,Rank+Rn+1);
	for(int i=2;i<=Rn;++i) if(Rank[i]!=Rank[i-1]) Rank[++I]=Rank[i];
	Rn=I;
}
int GetRank(int x){
	int L=1,R=Rn;//[L,R] first >=x
	while(L^R){
		int M=(L+R)>>1;
		if(Rank[M]<x) L=M+1;
		else R=M; 
	}
	return L;
}
//Information
struct Window{
	int A,B,C;//[A,B)--C
	Window(){}
	Window(int A,int B,int C):A(A),B(B),C(C){}
}W[maxn];int Wn;
struct Ad{
	int L,R;//[L,R)
	Ad(){}
	Ad(int L,int R):L(L),R(R){}
}A[maxn];int An;
//SweepLine
struct SweepLine{
	int X,ID;
	bool TYPE,IN;//type1=Ad type0=Window 
	SweepLine(){}
	SweepLine(int X,int ID,int TYPE,int IN):X(X),ID(ID),TYPE(TYPE),IN(IN){}
	bool operator <(const SweepLine &B)const{return X < B.X || X==B.X && TYPE < B.TYPE;} 
}Line[maxn<<2];int Ln;
int n,m;
int main(void)
{
	while(~scanf("%d%d",&n,&m)){
		Ln=Rn=An=Wn=0;
		for(int i=1;i<=n;++i){
			int l,r;scanf("%d%d",&l,&r);
			Rank[++Rn]=l;
			Rank[++Rn]=r;
			A[++An]=Ad(l,r);
		}
		for(int i=1;i<=m;++i){
			int a,b,c;scanf("%d%d%d",&a,&b,&c);
			Rank[++Rn]=a;
			Rank[++Rn]=b;
			W[++Wn]=Window(a,b,c);
		}
		//離散化
		SortRank();
		//掃描線
		for(int i=1;i<=n;++i){
			Line[Ln++]=SweepLine(GetRank(A[i].L),i,1,1);
			Line[Ln++]=SweepLine(GetRank(A[i].R),i,1,0);
		}
		for(int i=1;i<=m;++i){
			Line[Ln++]=SweepLine(GetRank(W[i].A),i,0,1);
			Line[Ln++]=SweepLine(GetRank(W[i].B),i,0,0);
		}
		sort(Line,Line+Ln);
		
		Build(Rn);//線段樹初始化 
		LL ANS=0;int I=0,AID,WID;
		int RM=0,RID=0;
		for(int i=1;i<=Rn;++i){//第一次掃描線,從左到右,計算第一,二類情況
			if(Rank[i]>RM) RM=Rank[i],RID=0;
			while(I < Ln && Line[I].X == i){
			 	if(Line[I].TYPE){//更新 
			 		if(Line[I].IN){//L操作 
			 			if(A[Line[I].ID].R>RM) RM=A[Line[I].ID].R,RID=Line[I].ID;
			 		}
			 		else{//R操作 
			 			Update(GetRank(A[Line[I].ID].L),Node(A[Line[I].ID].R-A[Line[I].ID].L,Line[I].ID));
			 		}
			 	}
			 	else{//計算 
			 		if(Line[I].IN){//A操作 
			 			if(RID){
				 			LL D=((LL) W[Line[I].ID].C)*(min(RM,W[Line[I].ID].B)-W[Line[I].ID].A);
					 		if(D > ANS){
					 			ANS=D;
					 			WID=Line[I].ID;
					 			AID=RID;
					 		}	
			 			}
			 		}
			 		else{//B操作 
			 			Node X=Query(GetRank(W[Line[I].ID].A),GetRank(W[Line[I].ID].B));
			 			if(X.V){
				 			LL D=((LL) W[Line[I].ID].C)*X.V;
				 			if(D > ANS){
				 				ANS=D;
				 				WID=Line[I].ID;
				 				AID=X.ID;
				 			}
			 			}
			 		}
			 	}
			 	++I;
			}
		}
		I=Ln-1;int LM=Rank[Rn],LID=0;
		for(int i=Rn;i>=1;--i){//第二次掃描線,從右往左,計算第三類情況
			if(Rank[i]<LM) LM=Rank[i],LID=0;
			while(I>=0 && Line[I].X == i){
			 	if(Line[I].TYPE){//更新 
			 		if(!Line[I].IN) {//R操作 
			 			if(A[Line[I].ID].L <LM) LM=A[Line[I].ID].L,LID=Line[I].ID;
			 		}
			 	}
			 	else{//計算 
			 		if(!Line[I].IN){//B操作 
			 			if(LID){
				 			LL D=((LL) W[Line[I].ID].C)*(W[Line[I].ID].B-max(W[Line[I].ID].A,LM));
					 		if(D > ANS){
					 			ANS=D;
					 			WID=Line[I].ID;
					 			AID=LID;
					 		}	
			 			}
			 		}
			 	}
			 	--I;
			}
		}
		printf("%I64d\n",ANS);
		if(ANS) printf("%d %d\n",AID,WID);
	}
return 0;
}



將所有線段都加入掃描線,每條線段都出現兩次:一次入邊, 一次出邊。

以下就用L,R代表無權值線段的入邊,出邊。用A,B代表有權值線段的入邊,出邊。

掃描線的過程,就是將所有的A,B,L,R排序,然後從小到大掃描一遍。

那麼,整個過程實際上,就是在A,B,L,R這四個端點各需要進行什麼操作的問題。

發佈了96 篇原創文章 · 獲贊 375 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章