HDU4343Interval query 倍增
去博客园看该题解
题意
给定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]; 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; else alive[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; else le=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); 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; }