洛谷P2487 cdq分治优化偏序线性dp

本文介绍了一种导弹拦截策略的算法实现,旨在找到能够拦截最多导弹数的方案,并计算每枚导弹被拦截的概率。采用最长下降子序列算法并结合CDQ分治优化DP,实现了O(n log n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

敌军即将按顺序发射 n n n枚导弹,每枚导弹有一个发射高度 h h h和发射速度 v v v,我方的导弹拦截系统尚未完善,不能完全拦截,目前每次拦截的导弹的高度和速度不能大于上一次拦截的导弹。为了使国家损失最小化,我们当然是选择一种能拦截最多导弹数的拦截方法拦截,如果有多种拦截方法,那么会随机选择一种方案实施,求敌军每颗导弹被拦截的概率

方法:

这就是求一个最长下降子序列,按照以往的方法,设 f [ i ] f[i] f[i]为最后一发拦截第 i i i发导弹的最大拦截数是多少,那么转移应该是这样子的:

f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) , j ∈ [ 0 , i − 1 ] , h [ j ] ≥ h [ i ] , v [ j ≥ v [ i ] ] f[i]=max(f[i],f[j]+1),j\in[0,i-1],h[j]\geq h[i],v[j\geq v[i]] f[i]=max(f[i],f[j]+1),j[0,i1],h[j]h[i],v[jv[i]]

为了求概率,我们还想要知道有多少最长的这样的子序列经过某个地方,于是有一个技巧,设 f [ i ] f[i] f[i]为最后一发拦截第 i i i发导弹的最大拦截数, g [ i ] g[i] g[i]为第一发拦截 i i i导弹的最大拦截数,同时 f t o t [ i ] ftot[i] ftot[i]为最后一发拦截第 i i i发导弹且拦截数最大的方案有多少种, g t o t [ i ] gtot[i] gtot[i]为第一发拦截第 i i i发导弹且拦截数最大的方案有多少种。这样设的好处是我们能通过这样知道是否存在有全局的最长子序列通过 i i i处,只需要判断是否有 f [ i ] + g [ i ] − 1 = = m a x ( f [ i ] ) f[i]+g[i]-1==max(f[i]) f[i]+g[i]1==max(f[i])即可,那么经过这里的条数自然就是 f t o t [ i ] ∗ g t o t [ i ] ftot[i]*gtot[i] ftot[i]gtot[i]

于是,总转移方程为

f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) , h [ j ] ≥ h [ i ] , v [ j ] ≥ v [ i ] , j ∈ [ 0 , i − 1 ] , , f t o t 随 f [ i ] 取大而继承 f[i]=max(f[i],f[j]+1),h[j]\geq h[i],v[j]\geq v[i],j\in[0,i-1],,ftot随f[i]取大而继承 f[i]=max(f[i],f[j]+1),h[j]h[i],v[j]v[i],j[0,i1],,ftotf[i]取大而继承

g [ i ] = m a x ( g [ i ] , g [ j ] + 1 ) , j ∈ [ i + 1 , n + 1 ] , h [ j ] ≤ h [ i ] , v [ j ≤ v [ i ] ] , g t o t 随 g [ i ] 取大继承 g[i]=max(g[i],g[j]+1),j\in[i+1,n+1],h[j]\leq h[i],v[j\leq v[i]],gtot随g[i]取大继承 g[i]=max(g[i],g[j]+1),j[i+1,n+1],h[j]h[i],v[jv[i]],gtotg[i]取大继承

这样的转移是 O ( n 2 ) O(n^2) O(n2)的,我们考虑优化他, g g g是一种 f f f逆序的转移,接下来只说明 f f f的优化

每个 i i i都只能被前面的满足 h [ j ] ≥ h [ i ] , v [ j ] ≥ v [ i ] h[j]\geq h[i],v[j]\geq v[i] h[j]h[i],v[j]v[i]的转移过来,即被满足这种偏序关系的点转移,偏序考虑 c d q cdq cdq分治,每次使用 [ l , m i d ] [l,mid] [l,mid]来更新 [ m i d + 1 , r ] [mid+1,r] [mid+1,r],更一般的,我们应该是被如下偏序关系的点 j j j更新

p o s [ j ] < p o s [ i ] (1) pos[j]<pos[i]\tag{1} pos[j]<pos[i](1)

h [ j ] ≥ h [ i ] (2) h[j]\geq h[i]\tag{2} h[j]h[i](2)

v [ j ] ≥ v [ i ] (3) v[j]\geq v[i]\tag{3} v[j]v[i](3)

i ∈ [ m i d + 1 , r ] , j ∈ [ l , m i d ] i\in[mid+1,r],j\in[l,mid] i[mid+1,r],j[l,mid],我们需要用 j j j更新 i i i,按照 c d q cdq cdq分治,第一维排序,第二维分治时隔开排序,此时保证前两维符合偏序条件,只需把所有符合 h [ j ] > = h [ i ] h[j]>=h[i] h[j]>=h[i] j j j的贡献统计出来,就可以更新 f [ i ] f[i] f[i]。我们需要的是所有 j ∈ [ l , m i d ] , h [ j ] ≥ h [ i ] , f [ j ] = m a x ( f [ k ] ) , k ∈ [ l , m i d ] j\in[l,mid],h[j]\geq h[i],f[j]=max(f[k]),k\in[l,mid] j[l,mid],h[j]h[i],f[j]=max(f[k]),k[l,mid]来更新,最大值来更新 f [ i ] f[i] f[i],所有最大值点的 f t o t ftot ftot的和来更新 f t o t [ i ] ftot[i] ftot[i]。于是考虑做一个后缀 m a x max max的树状数组, t r e e [ x ] tree[x] tree[x]代表最后一个结尾的 h h h x x x f f f最大是 t r e e [ x ] tree[x] tree[x],由于需要维护所有最大值的 f t o t ftot ftot和,那么多加一个树状数组,当 t r e e [ x ] tree[x] tree[x]取大时继承答案

tips:

后缀树状数组把 x ≤ n x\leq n xn x > 0 x>0 x>0互换, x − = l o w b i t ( x ) x-=lowbit(x) x=lowbit(x) x + = l o w b i t ( x ) x+=lowbit(x) x+=lowbit(x)互换即可

c d q cdq cdq分治优化 d p dp dp必须先 s o l v e ( l , m i d ) solve(l,mid) solve(l,mid),然后用 [ l , m i d ] [l,mid] [l,mid]更新 [ m i d + 1 , r ] [mid+1,r] [mid+1,r],再 s o l v e ( m i d + 1 , r ) solve(mid+1,r) solve(mid+1,r),如果不这样,存在一种情况设 m m i d = ( m i d + 1 + r ) / 2 mmid=(mid+1+r)/2 mmid=(mid+1+r)/2 i ∈ [ l , m i d ] , j ∈ [ m i d + 1 , m m i d ] , k ∈ [ m m i d + 1 , r ] i\in[l,mid],j\in[mid+1,mmid],k\in[mmid+1,r] i[l,mid],j[mid+1,mmid],k[mmid+1,r],使 i i i接上 j j j接上 k k k,如果不按照这样的方法,就不存在这样的可能了,只存在 i ∈ [ l , m i d ] , j [ m i d + 1 , r ] i\in[l,mid],j[mid+1,r] i[l,mid],j[mid+1,r] i i i接上 j j j的情形

#include<bits/stdc++.h>
#define double long double
using namespace std;

int n;
inline int lowbit(int x){return -x&x;}
using ll=long long;

struct fenwick1
{
    int tree1[100005];
    double tree2[100005];
    void add(int x,int v,double tot)
    {
        while(x)
        {
            if(v>tree1[x])
            {
                tree1[x]=v;
                tree2[x]=tot;
            }
            else if(v==tree1[x]) tree2[x]+=tot;
            x-=lowbit(x);
        }
    }
    pair<int,double> query(int x)
    {
        pair<int,double>ret={-1,-1};
        while(x<=n)
        {
            if(tree1[x]>ret.first)
            {
                ret.first=tree1[x];
                ret.second=tree2[x];
            }
            else if(tree1[x]==ret.first) ret.second+=tree2[x];
            x+=lowbit(x);
        }
        return ret;
    }
    void clear(int x)
    {
        while(x)
        {
            tree1[x]=tree2[x]=0;
            x-=lowbit(x);
        }
    }
}tree1;

struct fenwick2
{
    int tree1[100005];
    double tree2[100005];
    void add(int x,int v,double tot)
    {
        while(x<=n)
        {
            if(v>tree1[x])
            {
                tree1[x]=v;
                tree2[x]=tot;
            }
            else if(v==tree1[x]) tree2[x]+=tot;
            x+=lowbit(x);
        }
    }
    pair<int,double> query(int x)
    {
        pair<int,double>ret={-1,-1};
        while(x)
        {
            if(tree1[x]>ret.first)
            {
                ret.first=tree1[x];
                ret.second=tree2[x];
            }
            else if(tree1[x]==ret.first) ret.second+=tree2[x];
            x-=lowbit(x);
        }
        return ret;
    }
    void clear(int x)
    {
        while(x<=n)
        {
            tree1[x]=tree2[x]=0;
            x+=lowbit(x);
        }
    }
}tree2;

struct node
{
    int t,v,h;
}tmp[200005],a[200005];
int vv[200005],cnt,f[200005],g[200005];
double ftot[200005],gtot[200005];

void solve(int l,int r)
{
    if(l==r) return;
    int mid=l+r>>1;
    solve(l,mid);
    for(int i=l;i<=r;i++) a[i]=tmp[i];
    sort(a+l,a+mid+1,[&](const node& x,const node& y){
        return x.h>y.h;
    }); 
    sort(a+mid+1,a+r+1,[&](const node& x,const node& y){
        return x.h>y.h;
    });
    for(int i=mid+1,j=l-1;i<=r;i++)
    {
        while(j+1<=mid&&a[j+1].h>=a[i].h)
        {
            j++;
            tree1.add(a[j].v,f[a[j].t],ftot[a[j].t]);
        }
        auto tmp=tree1.query(a[i].v);
        if(f[a[i].t]<tmp.first+1)
        {
            f[a[i].t]=tmp.first+1;
            ftot[a[i].t]=tmp.second;
        }
        else if(f[a[i].t]==tmp.first+1) ftot[a[i].t]+=tmp.second;
    }
    for(int i=l;i<=mid;i++) tree1.clear(a[i].v);
    solve(mid+1,r);
}

void solve2(int l,int r)
{
    if(l==r) return;
    int mid=l+r>>1;
    solve2(l,mid);
    for(int i=l;i<=r;i++) a[i]=tmp[i];
    sort(a+l,a+mid+1,[&](const node& x,const node& y){
        return x.h<y.h;
    }); 
    sort(a+mid+1,a+r+1,[&](const node& x,const node& y){
        return x.h<y.h;
    });
    for(int i=mid+1,j=l-1;i<=r;i++)
    {
        while(j+1<=mid&&a[j+1].h<=a[i].h)
        {
            j++;
            tree2.add(a[j].v,g[a[j].t],gtot[a[j].t]);
        }
        auto tmp=tree2.query(a[i].v);
        if(tmp.first+1==g[a[i].t]) gtot[a[i].t]+=tmp.second;
        else if(tmp.first+1>g[a[i].t])
        {
            g[a[i].t]=tmp.first+1;
            gtot[a[i].t]=tmp.second;
        }
    }
    for(int i=l;i<=mid;i++) tree2.clear(a[i].v);
    solve2(mid+1,r);
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&tmp[i].v,&tmp[i].h);
        vv[++cnt]=tmp[i].v;
    }
    sort(vv+1,vv+1+cnt);
    cnt=unique(vv+1,vv+1+cnt)-vv-1;
    for(int i=1;i<=n;i++)
    {
        tmp[i].t=i;
        tmp[i].v=lower_bound(vv+1,vv+1+cnt,tmp[i].v)-vv;
        f[i]=g[i]=ftot[i]=gtot[i]=1;
    }
    solve(1,n);
    reverse(tmp+1,tmp+1+n);
    solve2(1,n);
    double tot=0; int max1=-1;
    for(int i=1;i<=n;i++) max1=max(max1,f[i]);
    for(int i=1;i<=n;i++)
        if(f[i]==max1) tot+=ftot[i];
    printf("%d\n",max1);
    for(int i=1;i<=n;i++)
    {
        double pp=0;
        if(f[i]+g[i]-1==max1) pp=1.0*ftot[i]*gtot[i]/tot;
        printf("%.5Lf ",pp);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值