zoukankan      html  css  js  c++  java
  • hihocoder 1305

     题目链接:https://hihocoder.com/problemset/problem/1305

    时间限制:10000ms
    单点时限:1000ms
    内存限制:256MB

    描述

    给定两个区间集合 A 和 B,其中集合 A 包含 N 个区间[ A1A2 ], [ A3A4 ], ..., [ A2N-1A2N ],集合 B 包含 M 个区间[ B1B2 ], [ B3B4 ], ..., [ B2M-1B2M ]。求 A - B 的长度。

    例如对于 A = {[2, 5], [4, 10], [14, 18]}, B = {[1, 3], [8, 15]}, A - B = {(3, 8), (15, 18]},长度为8。

    输入

    第一行:包含两个整数 N 和 M (1 ≤ NM ≤ 100000)。

    第二行:包含 2N 个整数 A1A2, ..., A2N (1 ≤ Ai ≤ 100000000)。

    第三行:包含 2M 个整数 B1B2, ..., B2M (1 ≤= Bi ≤ 100000000)。

    输出

    一个整数,代表 A - B 的长度。

    样例输入
    3 2
    2 5 4 10 14 18
    1 3 8 15
    样例输出
    8

    题解:

    一开始我的想法很简单,离散化后用区间修改单点查询的BIT,得到标记了所有还在的点,统计一下之后直接求和即可;

    于是乎我我就有了一个和标程对拍了10086组数据,但依然WA的程序:

    #include<cstdio>
    #include<algorithm>
    #define MAXN 400010
    using namespace std;
    int n,m;
    struct Inte{
        int l,r;
    }inte[MAXN/2];
    
    struct Discr{//离散化模板
        int _size;
        int idx[MAXN];//离散化索引
        void init(){_size=0;}
        void push(int val){idx[_size++]=val;}
        void discretize()
        {
            sort(idx,idx+_size);
            _size=unique(idx,idx+_size)-idx;
        }
        int id(int val){return lower_bound(idx,idx+_size,val)-idx+1;}//若有"+1"则离散化后的值从1开始,否则从0开始.
        int val(int id){return idx[id-1];}//若离散化后的值从1开始,则需要"-1".
    }discr;
    
    //BIT - 区间增加,单点查询 - st
    struct _BIT{
        int N;
        long long C[MAXN];
        int lowbit(int x){return x&(-x);}
        void init(int n)//初始化共有n个点
        {
            N=n;
            for(int i=1;i<=N;i++) C[i]=0;
        }
        long long query(int pos)//查询点pos的值
        {
            long long ret=0;
            while(pos<=N)
            {
                ret+=C[pos];
                pos+=lowbit(pos);
            }
            return ret;
        }
        void add(int pos,long long val)//区间1~pos加上val
        {
            while(pos>0)
            {
                C[pos]+=val;
                pos-=lowbit(pos);
            }
        }
    }BIT;
    //BIT - 区间增加,单点查询 - ed
    
    int endpoint[MAXN];
    int main()
    {
        freopen("input.txt","r",stdin);
        freopen("output.txt","w",stdout);
    
        scanf("%d%d",&n,&m);
        discr.init();
        for(int i=1,l,r;i<=n;i++)
        {
            scanf("%d%d",&l,&r);
            inte[i].l=l, inte[i].r=r;
            discr.push(inte[i].l);
            discr.push(inte[i].r);
        }
        for(int i=n+1,l,r;i<=n+m;i++)
        {
            scanf("%d%d",&l,&r);
            inte[i].l=l, inte[i].r=r;
            discr.push(inte[i].l);
            discr.push(inte[i].r);
        }
        discr.discretize();
    
        BIT.init(discr._size+1);
        for(int i=1;i<=n;i++)
        {
            int l=discr.id(inte[i].l);
            int r=discr.id(inte[i].r);
            BIT.add(r,1);
            BIT.add(l-1,-1);
        }
        for(int i=n+1;i<=n+m;i++)
        {
            int l=discr.id(inte[i].l)+1;
            int r=discr.id(inte[i].r)-1;
            if(r<l) continue;
            BIT.add(r,-100003);
            BIT.add(l-1,100003);
        }
    
        int cnt=0;
        long long now=0,nex=BIT.query(1);
        if(now<=0 && nex>0) endpoint[cnt++]=1;
    
        for(int i=1;i<=discr._size;i++)
        {
            now=nex;
            nex=BIT.query(i+1);
            if(i+1>discr._size) nex=0;
            //printf("%d -> %d = %lld
    ",discr.val(i),i,now);
    
            if(now<=0 && nex>0) endpoint[cnt++]=i+1;
            else if(now>0 && nex<=0) endpoint[cnt++]=i;
        }
    
        int ans=0;
        for(int i=0;i<cnt;i+=2)
        {
            int l=discr.val(endpoint[i]);
            int r=discr.val(endpoint[i+1]);
            //printf("%d %d
    ",l,r);
            ans+=r-l;
        }
        printf("%d
    ",ans);
    }
    //你问我服不服,当然是不服的
    View Code

    那就老老实实按题解说的做呗;

    官方题解https://hihocoder.com/discuss/question/4554

    这道题是一类区间问题的变体,我们先来看一道最基础的区间问题:

    给定N个区间[S1, E1], [S2, E2], ... [SN, EN],求这些区间并集的长度。

    这道题通常的解法是,我们把这N个区间的2N个端点从左到右排列在数轴上P1, P2, ... P2N。并且如果一个点Pi是原区间的左端点,我们就把它标记成绿色;如果是右端点,就标记成蓝色。

    值得注意的是这2N个点中可能存在重合的点。比如假设有两个区间[1, 3]和[3, 5],那么在3这个位置上就同时存在一个绿点(左端点)和蓝点(右端点)。某些情况下我们在排序时需要特别处理重合的点,例如要保证蓝点都排在绿点之前。不过本题我们无需特殊处理,重合的点无论谁在前谁在后都不影响结果。

    这2N个点把数轴分成了2N+1段,(-INF, P1), (P1, P2), (P2, P3) ... (P2N-1, P2N), (P2N, +INF)。每一段内部被原来区间集合覆盖的情况都是相同的。换句话说,不会出现(Pi, Pi+1)的左半部分被第1、3、5号区间覆盖,而右半部分只被第1、3号区间覆盖这种情况。

    所以我们可以从左到右扫描每一段,令cnt计数器初始值=0。当扫过一个绿点时,cnt++;扫过一个蓝点时cnt--。我们可以发点对于(Pi, Pi+1)这一段,处理完Pi时的cnt值恰好代表了这一段被几个原来的区间同时覆盖。

    有了每一段的cnt值,我们可以做很多事情。例如要求区间并集的长度,我们可以找出所有cnt值大于0的段(Pi, Pi+1),并把这些段的长度(Pi+1 - Pi)求和。

    我们还可以知道哪段被覆盖了最多次:自然是cnt值最大的段。

    对于给定的坐标X,我们可以在O(logN)的时间内求出X这个点被覆盖多少次:我们只需要在P1, P2, ... P2N中二分查找出X的位置,即Pi < X < Pi+1,那么(Pi, Pi+1)这一段的cnt值就是答案。(当X恰好是端点时需要特判,取决于给出的区间是开区间还是闭区间)

    好了,我们回到《区间求差》这道题目。我们可以把A和B集合中2N+2M个端点都从左到右排列在数轴上。并且用4种颜色标记出每个点是A的左端点、A的右端点、B的左端点、B的右端点。

    然后我们用两个计数器cntA和cntB来分别维护每一段被A集合中的区间覆盖多少次、以及被B集合的区间覆盖多少次。那么如果某一段(Pi, Pi+1)满足cntA>0且cntB=0,那么它一定是A-B的一部分。我们对于这些段的长度求和即可。

    整个算法对于端点排序的部分复杂度是O(NlogN)的,对于从左到右扫描复杂度是O(N)的。总体复杂度是O(NlogN)。

    有了这么详细的题解后,敲成代码就不难了;

    AC代码:

     1 #include<bits/stdc++.h>
     2 #define MAX 100010
     3 #define INF 0x3f3f3f3f
     4 using namespace std;
     5 
     6 int n,m;
     7 
     8 struct Point{
     9     int pos;
    10     int type;//1-Al,2-Ar;3-Bl,4-Br.
    11 }point[4*MAX];
    12 bool cmp(Point a,Point b){return a.pos<b.pos;}
    13 
    14 struct Interval{
    15     int cntA,cntB;
    16 }interval[4*MAX];
    17 int main()
    18 {
    19     scanf("%d%d",&n,&m);
    20     int _size=0;
    21     for(int i=1,l,r;i<=n;i++)
    22     {
    23         scanf("%d%d",&l,&r);
    24         point[_size++]=(Point){l,1};
    25         point[_size++]=(Point){r,2};
    26     }
    27     for(int i=1,l,r;i<=m;i++)
    28     {
    29         scanf("%d%d",&l,&r);
    30         point[_size++]=(Point){l,3};
    31         point[_size++]=(Point){r,4};
    32     }
    33     sort(point,point+_size,cmp);
    34     //for(int i=0;i<_size;i++) printf("pos=%d type=%d
    ",point[i].pos,point[i].type);
    35 
    36     int ans=0;
    37     interval[0]=(Interval){0,0};
    38     for(int i=1;i<_size;i++)
    39     {
    40         Point& lp=point[i-1];
    41         Point& rp=point[i];
    42 
    43         if(lp.type==1)//遇到一个A集合中的左端点
    44         {
    45             interval[i].cntA=interval[i-1].cntA+1;
    46             interval[i].cntB=interval[i-1].cntB;
    47         }
    48         if(lp.type==2)//遇到一个A集合中的右端点
    49         {
    50             interval[i].cntA=interval[i-1].cntA-1;
    51             interval[i].cntB=interval[i-1].cntB;
    52         }
    53         if(lp.type==3)//遇到一个B集合中的左端点
    54         {
    55             interval[i].cntA=interval[i-1].cntA;
    56             interval[i].cntB=interval[i-1].cntB+1;
    57         }
    58         if(lp.type==4)//遇到一个B集合中的右端点
    59         {
    60             interval[i].cntA=interval[i-1].cntA;
    61             interval[i].cntB=interval[i-1].cntB-1;
    62         }
    63 
    64         //printf("%d - %d : cntA=%d cntB=%d
    ",lp.pos,rp.pos,interval[i].cntA,interval[i].cntB);
    65         if(interval[i].cntA>0 && interval[i].cntB==0) ans+=rp.pos-lp.pos;
    66     }
    67 
    68     printf("%d
    ",ans);
    69 }
  • 相关阅读:
    localStorage cache
    webpack的学习过程
    npm install --save/--save-dev的区别
    .gitignore常见问题
    jQuery的优点与缺点
    JSONP是什么
    Node.js-Usage & Example
    【转】Swig Getting Started
    【转】使用Spring MVC统一异常处理实战
    C++-Qt【5】-QT的QString,char*,QByteArray转化以及中文乱码的问题
  • 原文地址:https://www.cnblogs.com/dilthey/p/7679274.html
Copyright © 2011-2022 走看看