• 牛客网 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;
    }

      

  • 相关阅读:
    《中小学生Python编程入门指南》1.1 什么是编程
    《中小学生Python编程入门指南》前言
    简单的番茄工作法倒计时(源码)
    关于AE
    Blender2.5快捷键
    关于Blender
    随意设置控件每一个角的倒角
    关于多个block问题
    UICollectionViewCell--查找cell上的按钮点击后,对应的是哪个cell
    UIMenuItem
  • 原文地址:https://www.cnblogs.com/a249189046/p/8799146.html
Copyright © 2020-2023  润新知