给定一个数轴,给定n条线段,每条线段可以覆盖[li,ri]的位置,q次询问,每次问如果要覆盖[l,r],最少要选多少条线段。
先考虑一个贪心的暴力解法:
对于询问[L,R],假设当前位置为l,我们要从左端点小于等于l的线段中找到一个最大的右端点r,并让当前位置更新为r,直到R==r
。
尽管我们可以预处理得到a[i]
表示从i位置只选一个线段能到达右边最远的位置是哪里, 暴力解法时间复杂度仍为 O(nq).
既然i位置能够选一条线段最远到达a[i],同时a[i]位置能选一条线段最远到达a[a[i]],那么i位置可以选x条线段最远到达的地方也是可以求的,(相当于我们知道每个位置走一步到达的地方,那么就可以求出每个位置走k步到达的地方),但是遍历x次去求太慢了。
我们把x写成二进制,假设为1011,那么我们只要知道i走1000,10,1步到达的地方,就可以求出x步的答案了,这就是倍增的思想,通过二进制表示使得遍历[1,x]的时间复杂度降到logx .
关于倍增的讲解可以参考:大佬的文章
回到这个问题,我们通过倍增在nlogn的预处理时间内,求出了每个点选x条线段能够到达的最远地方,那么每次询问,我们就从l位置出发,求出这个最小的x。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 5e2 + 10;
const ll mod = 1e9 + 7;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
while(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
while(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
//给定m个线段 覆盖[li,ri] q次询问 每次询问[l,r]被覆盖至少需要多少个线段
//暴力 我们从出发点l开始 找到左端点小于等于l的线段 右端点最大值 并以之更新出发点 直到大于等于r
//我们可以求出从数轴上i出发 只经过一条线段最远能到达的地方
//通过倍增 求出从i出发经过2^j条线段能到达的最远地方
//倍增查询
ll a[maxn];//从i出发 只经过一条线段能到达最远的地方
ll to[maxn][22];
int n,m;
int mx;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1,x,y;i<=n;i++)
{
scanf("%d %d",&x,&y);
a[x]=max(a[x],1ll*y);
mx=max(mx,y);
}
for(int i=1;i<=mx;i++) a[i]=max(a[i-1],a[i]);
for(int i=0;i<=mx;i++) to[i][0]=a[i];
for(int j=1;j<=20;j++)
{
for(int i=0;i<=mx;i++)
{
to[i][j]=to[to[i][j-1]][j-1];
}
}
//下面的写法是错误的,dp应该是按j一层一层转移
// for(int i=0;i<=mx;i++)
// {
// for(int j=1;j<=20;j++)
// {
// to[i][j]=to[to[i][j-1]][j-1];
// }
// }
while(m--)
{
int l,r;
scanf("%d %d",&l,&r);
int ans=0;
//找到最小的x 使得l选x条线段可以到达y满足a[i]>=r
for(int i=20;i>=0;i--)
{
if(to[l][i] < r)
{
ans+=(1<<i);
l=to[l][i];
}
}
if(to[l][0]>=r) printf("%d\n",ans+1);
else puts("-1");
}
return 0;
}