E. Minimal Segment Cover (贪心+倍增)

传送门

给定一个数轴,给定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;
}

猜你喜欢

转载自blog.csdn.net/qq_46030630/article/details/121276899