题目:
给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; }
/* 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这四个端点各需要进行什么操作的问题。