[BZOJ2244][SDOI2011]拦截导弹(DP+CDQ分治)

本文详细解析了一道SDOI竞赛题的求解方法,利用CDQ分治策略解决了最长不上升子序列问题,并考虑了元素个数统计。通过两次CDQ分治分别处理正序与倒序情况,实现了高效求解。

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

=== ===

这里放传送门

=== ===

题解

表示作为一个SD弱菜在写SDOI恶心题的时候真是有一种生无可恋的感觉。。

第一问这个DP可以发现跟什么偏序还是什么类似的问题很像总之好像是可以用CDQ分治来搞一搞的,那么就搞一搞吧,要求的这个“最长不上升子序列”有三个限制,一个是下标,一个是h,一个是v,那就在分治的时候递归完左半边然后处理左半边对右半边的影响,按h排一下序然后挂一个树状数组来搞定v就可以啦!

但是加上第二问就有点不大好了。。要统计的是包含某个元素的“最长不上升子序列”的元素个数,可以想到就是以这个元素结尾的序列个数乘上以这个元素开头的序列个数。一开始以为用CDQ分治搞完了f以后下来推一下g就可以,结果好像推g的时候也有三个限制所以还是要用CDQ。。咋办?再写一个CDQ?其实只要在搞f的时候一块搞了就可以。。然后还有,“以这个元素开头的”序列个数咋办?不得倒着再来一遍?再写一个CDQ?其实带个参合并了就好,但是相对的代码就变得非常恶心。。

代码

请自动忽略代码里某些除了增加常数以外没什么卵用的部分。。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,p[50010],X,Y,val[50010],L,R,head,tail,ans;
double s[50010],tot;
struct missile{
    int h,v,f[2],id[2];
    double g[2];
}t[50010],tmp[50010];
int Hcmp(int x,int y){return t[x].h<t[y].h;}
int Vcmp(int x,int y){return t[x].v<t[y].v;}
int cmp(missile a,missile b){return a.h>b.h||(a.h==b.h&&a.id[0]<b.id[0]);}
int Icmp(missile a,missile b){return a.id[0]<b.id[0];}
void Hash(){
    sort(p+1,p+n+1,Hcmp);
    for (int i=1;i<=n;i++)
      if (t[p[i]].h==t[p[i-1]].h)
        val[p[i]]=X;
      else val[p[i]]=++X;
    for (int i=1;i<=n;i++){
        t[i].h=val[i];p[i]=i;
    }
    sort(p+1,p+n+1,Vcmp);
    for (int i=1;i<=n;i++)
      if (t[p[i]].v==t[p[i-1]].v)
        val[p[i]]=Y;
      else val[p[i]]=++Y;
    for (int i=1;i<=n;i++) t[i].v=val[i];
}
int lowbit(int i){return i&(-i);}
void add(int i,int val,double G){
    while (i<=n){
        if (p[i]<val){
            p[i]=val;s[i]=G;
        }else if (p[i]==val) s[i]+=G;
        i+=lowbit(i);
    }
}
int ask(int i,double &G){
    long long ans=0;G=0;
    while (i!=0){
        if (ans<p[i]){ans=p[i];G=s[i];}
        else if (p[i]==ans) G+=s[i];
        i-=lowbit(i);
    }
    return ans;
}
void clear(int i){
    while (i<=n){s[i]=p[i]=0;i+=lowbit(i);}
}
bool Comp(missile a,missile b,int opt){
    if (opt==0)
      return (a.h>b.h||(a.h==b.h&&a.id[opt]<b.id[opt]));
    else return (a.h<b.h||(a.h==b.h&&a.id[opt]<b.id[opt]));
}
int V(int val,int opt){
    if (opt==0) return n-val+1;
    else return val;
}//计算当前状态下应该带入树状数组的下标
void Divide(int l,int r,int opt){
    if (l==r){
        t[l].f[opt]=max(t[l].f[opt],1);
        if (t[l].f[opt]==1) t[l].g[opt]=1;
        return;//如果它是一个合法序列的开头,那么要给g赋初值

    }
    int mid=(l+r)>>1,p1,p2;
    p1=l;p2=mid+1;
    for (int i=l;i<=r;i++)
      if (t[i].id[opt]<=mid) tmp[p1++]=t[i];
      else tmp[p2++]=t[i];
    for (int i=l;i<=r;i++) t[i]=tmp[i];
    Divide(l,mid,opt);
    p1=l;p2=mid+1;
    for (int i=l;i<=r;i++)
      if (p2>r||(p1<=mid&&Comp(t[p1],t[p2],opt)))
        tmp[i]=t[p1++];
      else tmp[i]=t[p2++];
    for (int i=l;i<=r;i++) t[i]=tmp[i];
    for (int i=l;i<=r;i++)
      if (t[i].id[opt]<=mid)
        add(V(t[i].v,opt),t[i].f[opt],t[i].g[opt]);
      else{
          double tmpg=0;
          int tmpf=ask(V(t[i].v,opt),tmpg)+1;
          if (tmpf>t[i].f[opt]){//判断是否更新f,更新f的同时要更新g
              t[i].f[opt]=tmpf;t[i].g[opt]=tmpg;
          }else if (tmpf==t[i].f[opt]) t[i].g[opt]+=tmpg;
      }
    for (int i=l;i<=r;i++)
      if (t[i].id[opt]<=mid) clear(V(t[i].v,opt));
    p1=l;p2=mid+1;
    for (int i=l;i<=r;i++)
      if (t[i].id[opt]<=mid) tmp[p1++]=t[i];
      else tmp[p2++]=t[i];//按照下标排序以便向后递归
    for (int i=l;i<=r;i++) t[i]=tmp[i];
    Divide(mid+1,r,opt);
    p1=l;p2=mid+1;
    for (int i=l;i<=r;i++)
      if (p2>r||(p1<=mid&&Comp(t[p1],t[p2],opt)))
        tmp[i]=t[p1++];
      else tmp[i]=t[p2++];
    for (int i=l;i<=r;i++) t[i]=tmp[i];
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
      scanf("%d%d",&t[i].h,&t[i].v);
      t[i].id[0]=p[i]=i;
      t[i].id[1]=n-i+1;
    }
    Hash();t[0].h=t[0].v=-1;
    memset(p,0,sizeof(p));
    sort(t+1,t+n+1,cmp);
    Divide(1,n,0);
    for (int i=1;i<=n/2;i++) swap(t[i],t[n-i+1]);//把序列翻转
    Divide(1,n,1);
    for (int i=1;i<=n;i++) ans=max(ans,t[i].f[0]);
    printf("%d\n",ans);
    sort(t+1,t+n+1,Icmp);
    for (int i=1;i<=n;i++)
      if (t[i].f[0]==ans) tot+=t[i].g[0];
    for (int i=1;i<=n;i++)
      if (t[i].f[0]+t[i].f[1]-1==ans)
        printf("%lf ",t[i].g[0]*t[i].g[1]/tot);
      else printf("0.000000 ");
    return 0;
}

偏偏在最后出现的补充说明

CDQ分治可以用来解决一些限制比较多的东西,因为分治本身能够消去一维的影响,然后通过排序还可以消去一维的影响,再加上一个一维的数据结构就可以做一些本来需要复杂数据结构才能做的东西了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值