您的位置:首页 > 其它

Codeforces 542A. Place Your Ad Here (扫描线进阶 带权值的线段交求最大值) (线段树)

2015-08-31 21:11 537 查看
题目:

给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这四个端点各需要进行什么操作的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线段树 codeforces