HDU4343Interval query 倍增

来源:互联网 发布:数控编程专业介绍 编辑:程序博客网 时间:2024/06/01 09:30

题意

  给定n个区间[a,b),都是左闭右开,有m次询问,每次询问你最多可以从n个区间中选出多少[L,R]的子区间,使得他们互不相交。 n,m<=10^5。 区间下标<=10^9。

题解

  这题要用倍增

  首先,给区间按照左端点编号排个序。

  如果区间A包含了区间B,那么A一定没用,扔了。

  那么剩余的区间[x,y]的x和y一定都是升序的。

  之后,就是对于区间的贪心了:

  找到一个区间[xi,yi]之后,一定是寻找一个xj>yi且xj最小的那个区间[xj,yj],所以设该区间的编号j=next[i];这个只要二分查找一下就可以了。

  那么贪心的时候就是不断的走next,这样就出现了一个O(nm)的算法。

  那么倍增怎么做呢?

  设nxt[i][j]为第i个区间next 2^j 次后的区间编号,那么:

  nxt[i][0]=next[i],nxt[i][j]=nxt[nxt[i][j-1]][j-1

  于是就可以做了。

 

  不过这里我要提醒一点:C++的变量名如果用了"next",在HDU是无法通过编译的,我因此贡献了8次CE……

代码

#include <cstring>#include <algorithm>#include <cstdio>#include <cstdlib>#include <cmath>using namespace std;const int N=100000+5;int n,m;int Next[N][20];bool alive[N];//原来的next[N]舍去,nxt[N][20]写作next[N][20]struct Seg{int x,y;bool operator < (const Seg &a) const{if (x==a.x)return y>a.y;return x<a.x;}}a[N];void Thrown(){int n_=0,miny=1e9+1;for (int i=n;i>=1;i--)if (a[i].y>=miny)alive[i]=0;elsealive[i]=1,miny=min(miny,a[i].y);for (int i=1;i<=n;i++)if (alive[i])a[++n_]=a[i];n=n_;}int findx(int x){int le=1,ri=n,mid,ans=n+1;while (le<=ri){mid=(le+ri)>>1;if (a[mid].x==x)return mid;if (a[mid].x>x)ri=mid-1,ans=mid;elsele=mid+1;}return ans;}int solve(int L,int R){int st=findx(L),ans=1,k;if (a[st].y>R)return 0;for (k=0;(1<<k)<=n-st;k++);for (;k>=0;k--)if ((1<<k)<=n-st&&a[Next[st][k]].y<=R)st=Next[st][k],ans+=1<<k;return ans;}int main(){while (~scanf("%d%d",&n,&m)){memset(Next,0,sizeof Next);for (int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y),a[i].y--; sort(a+1,a+n+1);Thrown();a[n+1].y=1e9+1;a[0].y=1e9+1;for (int i=n;i>=1;i--){Next[i][0]=findx(a[i].y+1);//即原来的next[i]for (int j=1;(1<<j)<=n-i;j++)Next[i][j]=Next[Next[i][j-1]][j-1];}for (int i=1,L,R;i<=m;i++){scanf("%d%d",&L,&R);printf("%d\n",solve(L,R));}}return 0;}



原创粉丝点击