牛客网 VVQ 与线段 (优先队列或线段树或RMQ)

链接:https://www.nowcoder.com/acm/contest/80/E
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
 64bit IO Format: %lld


题目描述


VVQ 最近迷上了线段这种东西 ,现在他手上有 n 条线段,他希望在其中找到两条有公共点的线段,使得他们的异或值最大。 定义线段的异或值为它们并的长度减他们交的长度 
输入描述:
第一行包括一个正整数 n,表示 VVQ 拥有的线段条数。接下来 n 行每行包括两个正整数 l,r,表示 VVQ 拥有的线段的 左右端点。
输出描述:
一行一个整数,表示能得到的最大异或值

示例1

输入

3
10 100
1 50
50 100

输出

99

说明

选择第二条和第三条,99-0=99


备注:
1<=n<=200000,1<=l<=r<=1e8

分析: 两条线段有公共点,分为两种情况: 

  1.线段a内部包含线段b          贡献为:  (a.y-a.x)-(b.y-b.x)

  2.部分覆盖的线段a和线段b,  贡献为 a.y+a.x-b.y-b.x;

  所以我们需要分别维护x+y和x-y的值,题目需要求最大的贡献,

  那么对于一条线段a,我们需要找到所有与它有公共点的线段中,贡献最大的。

解法一:优先队列

用两个优先队列分别维护x+y和x-y的最值

将所有线段分为 入点和出点,并将入点标记为-i,出点标记为i,并将这些点用以升序进行排列

然后将这些点,从左到右进行扫描,比如当遇到了某个入点的时候,首先考虑优先队列中能与它产生最大贡献的线段,

也就是队头的线段是否和它有公共点。如果在枚举的过程中,已经遇到过了该线段的出点,则一定没有公共点,并让该线段出队

,继续判断下一条线段。反之,则更新最优解。 完成上述操作后,在把枚举到的这条线段放入到优先队列中。

如果在枚举的过程中遇到的是出点,则只需要进行标记,供上述判定时使用。

代码如下(时间复杂度nlogn)

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
const int MAXN=4e5+10;
int t;
int vis[MAXN];
struct node
{
    int x;
    int y;
    node(int a=0,int b=0):x(a),y(b){};
}poi[MAXN],q[MAXN];
bool operator <(node a,node b)
{
    if(a.x!=b.x)
    return a.x<b.x;
    return a.y<b.y;
}
priority_queue<node>Q1;
priority_queue<node>Q2;
int main()
{
    int a,b,cnt,ans;
    ans=0;
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>t;
    cnt=1;
    for(int k=1;k<=t;k++)
    {
      cin>>a>>b;
      q[k].x=a;
      q[k].y=b;
      poi[cnt].x=a,poi[cnt].y=-k; //对于入点和出点的处理
      cnt++;
      poi[cnt].x=b,poi[cnt].y=k;
      cnt++;
    }

     sort(poi+1,poi+cnt);

    for(int i=1;i<cnt;i++)
    {
       if(poi[i].y>0)
       vis[poi[i].y]=1; //标记出点
       else
       {
          int k=-poi[i].y;
          while(!Q1.empty()&&vis[Q1.top().y])Q1.pop();  //已经遇到了的出点,则无公共点,出队
          while(!Q2.empty()&&vis[Q2.top().y])Q2.pop();

        if(!Q1.empty())  ans=max(ans,q[k].x+q[k].y+Q1.top().x);//有公共点,更新最优解
        if(!Q2.empty())  ans=max(ans,Q2.top().x-q[k].y+q[k].x);

          Q1.push(node(-q[k].x-q[k].y,k));
          Q2.push(node(q[k].y-q[k].x,k));
       }
    }
    cout<<ans<<endl;
    return 0;
}

解法二:线段树

首先将所有的线段按照x的升序排列,x相同的,按y的升序排列。

然后从依次枚举每一条线段,找到与它有公共点的线段的范围。

因为经过排列后,线段是有序的,所以可以采用二分方法来找到这个范围。

现在需要找到这个范围中  x+y和x-y的最值来更新最优解。

需要维护区间最值,可以采用线段树。

代码如下(时间复杂度(O(nlognlogn))

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN=2e5+100;
typedef long long ll;
#define INF 0x3f3f3f3f
int maxx1[MAXN<<2];
int maxx2[MAXN<<2];
int res1;
int res2;
struct Node{
   int x;
   int y;
}poi[MAXN];
bool cmp(Node a,Node b)
{
   if(a.x!=b.x)
    return a.x<b.x;
   return a.y<b.y;
}
struct node
{
    int l;
    int r;
}tree[MAXN<<2];
void PushUp(int rt)
{
  maxx1[rt]=max(maxx1[rt<<1],maxx1[rt<<1|1]);
  maxx2[rt]=max(maxx2[rt<<1],maxx2[rt<<1|1]);
}
void BuildTree(int l,int r,int rt)
{
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
    {
      maxx1[rt]=poi[l].x+poi[l].y;
      maxx2[rt]=poi[l].x-poi[l].y;
      return;
    }
    int mid=(tree[rt].l+tree[rt].r)/2;
    BuildTree(l,mid,rt<<1);
    BuildTree(mid+1,r,rt<<1|1);
    PushUp(rt);
}
 
void Query(int l,int r,int rt)
{
    if(tree[rt].l==l&&tree[rt].r==r)
    {
      res1=max(res1,maxx1[rt]);
      res2=max(res2,maxx2[rt]);
      return;
    }
    int mid=(tree[rt].l+tree[rt].r)/2;
    if(r<=mid)Query(l,r,rt<<1);
    else if(l>mid)Query(l,r,rt<<1|1);
    else
    {
        Query(l,mid,rt<<1);
        Query(mid+1,r,rt<<1|1);
    }
    PushUp(rt);
}
int main()
{
   int n,ans=0;
   scanf("%d",&n);
   for(int i=1;i<=n;i++)
    scanf("%d%d",&poi[i].x,&poi[i].y);
   sort(poi+1,poi+n+1,cmp);
   BuildTree(1,n,1);
 
   for(int i=1;i<=n;i++)
   {
       int ll=i;
       int rr=n+1;
       while(ll+1<rr)
       {
          int mid=(ll+rr)/2;
          if(poi[mid].x<=poi[i].y)
          ll=mid;
          else
          rr=mid;
       }
      if(ll==i)continue;
      res1=-INF;
      res2=-INF;
      Query(i+1,ll,1);
      ans=max(ans,res1-poi[i].x-poi[i].y);
      ans=max(ans,poi[i].y-poi[i].x+res2);
   }
    printf("%d
",ans);
    return 0;
}

解法三:RMQ 

将维护区间最值的方法换成RMQ即可

代码如下(时间复杂度(O(nlognlogn))

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN=2e5+100;
typedef long long ll;
#define INF 0x3f3f3f3f
int maxsum[MAXN][20];
int minsum[MAXN][20];
int maxx;
int minn;
struct Node{
   int x;
   int y;
}poi[MAXN];
 
bool cmp(Node a,Node b)
{
   if(a.x!=b.x)
    return a.x<b.x;
   return a.y<b.y;
}
 
void RMQ_In(int num) //预处理->O(nlogn)
{
    for(int j = 1; j < 20; ++j)
        for(int i = 1; i <= num; ++i)
            if(i + (1 << j) - 1 <= num)
            {
                maxsum[i][j] = max(maxsum[i][j - 1], maxsum[i + (1 << (j - 1))][j - 1]);
                minsum[i][j] = min(minsum[i][j - 1], minsum[i + (1 << (j - 1))][j - 1]);
            }
}
void RMQ_Query(int src,int des)
{
        if(src>des)
        swap(src,des);
        int k=(int)(log(des-src+1.0)/log(2.0));
        maxx=max(maxsum[src][k],maxsum[des-(1<<k)+1][k]);
        minn=min(minsum[src][k],minsum[des-(1<<k)+1][k]);
}
 
int main()
{
   int n,ans=0;
   scanf("%d",&n);
   for(int i=1;i<=n;i++)
    scanf("%d%d",&poi[i].x,&poi[i].y);
   sort(poi+1,poi+n+1,cmp);
   for(int i=1;i<=n;i++)
   {
      minsum[i][0]=poi[i].y-poi[i].x;
      maxsum[i][0]=poi[i].y+poi[i].x;
   }
   RMQ_In(n);
   for(int i=1;i<=n;i++)
   {
       int ll=i;
       int rr=n+1;
       while(ll+1<rr)
       {
          int mid=(ll+rr)/2;
          if(poi[mid].x<=poi[i].y)
          ll=mid;
          else
          rr=mid;
       }
      if(ll==i)continue;
      maxx=-INF;
      minn=INF;
      RMQ_Query(i+1,ll);
      ans=max(ans,maxx-poi[i].x-poi[i].y);
      ans=max(ans,poi[i].y-poi[i].x-minn);
   }
 
    printf("%d
",ans);
    return 0;
}

  

原文地址:https://www.cnblogs.com/a249189046/p/8799146.html