CDQ分治优化一维DP转移——[SDOI2011]拦截导弹
在这之前P1020 导弹拦截
Description [SDOI2011]拦截导弹
给出\(n\)个导弹,每个导弹三个属性\(t_i,h_i,v_i\),分别为时间,发射高度,速度。对于已经拦截的导弹 \(i\),下一个能被拦截的导弹\(j\)必须满足\(i<j,h_i\geq h_j,v_i\geq v_j\),请求出最多能拦截的导弹数量。对此,可能有多种方案,请再求出每个导弹被拦截的概率。
Solution
显然这是一个DP的题目。根据题意,可以得到DP方程\(dp_i=1+max(dp[j],\forall i>j,h_j\geq h_i,v_j\geq v_i)\),对于这个方程普通的DP需要时间\(O(n^2)\),需要优化,我们注意到后面三个条件类似于三维偏序,所以我们考虑
CDP分治优化一维DP转移
先考虑CDQ是什么。(我想只有我不知道),一种广泛的用法是处理点对有关问题,详情OI-Wiki-part1
对于普通的CDQ,处理区间\((l,r)\)顺序是这样的:
1.处理区间 \((l,mid)\)
2.处理区间\((mid+1,r)\)
3.处理\(l\leq i\leq mid,mid+1\leq j \leq r\)的关系
但是对于CDQ优化DP转移,我们需要顺序:1、3、2。
为什么呢?我们考虑DP顺序,对于正在更新的位置\(i\),我们需要将所有位置在它前面的值都更新完毕,所以需要先更新整个区间再更新其他的区间。
解决了优化DP的问题,我们可以求一个最长不升子序列就能得到第一问的答案。对于第二问,我们考虑记录总的方案数,用经过一个点的方案数除以总方案数\(sum\)就是答案。
考虑对于一个点\(i\),如果它在目标序列中却不是目标序列的终点,我们需要知道在它后面还有多少方案数能够到达目标序列,那么就可以将整个序列取反(就是大的变小小的变大)再求一遍最长不升子序列就能知道在它后面的方案数\(a[1][i].gl\),那么\((a[0][i].gl*a[1][i].gl)/sum\)就可以得到这个点出现的概率了。
Code(有注释在代码里)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define db double//不开double会炸qwq
#define int long long
using namespace std;
const int N=5e4+5;
struct node {
int h,v,ans,id;//ans表示以点i为结尾的最长不升子序列长度,gl为方案数
db gl;
}a[2][N];//a[0]是正着的,a[1]是反着的
int n,ls[N],tot;
struct Tree1{
int mx[N];
db gl[N];
inline void add (int x,int maxx,db g) {
while (x) {
if (maxx==mx[x]) gl[x]+=g;
else if(maxx>mx[x]) mx[x]=maxx,gl[x]=g;
x-=x&(-x);
}
}
inline void query (int x,int &mxx,db &g) {
while (x<=tot) {
if (mxx==mx[x]) g+=gl[x];
else if (mxx<mx[x]) mxx=mx[x],g=gl[x];
x+=x&(-x);
}
}
inline void del (int x) {
while (x) {
mx[x]=gl[x]=0;
x-=x&(-x);
}
}
}T;//树状数组求后缀
inline bool cmpid (node x,node y) {return x.id<y.id;}
inline bool cmph (node x,node y) {return x.h>y.h;}
inline bool cmp (node x,node y) {return x.id>y.id;}
inline void cdq (int l,int r,int ty) {
if (l==r) return;
int mid=(l+r)>>1;
cdq(l,mid,ty);
sort(a[ty]+mid+1,a[ty]+r+1,cmph);
int i=l,j=mid+1;
while (j<=r) {
while (a[ty][i].h>=a[ty][j].h&&i<=mid) {
T.add(a[ty][i].v,a[ty][i].ans,a[ty][i].gl);
i++;
}
int mx=0;db g=0;
T.query(a[ty][j].v,mx,g);
if (mx+1==a[ty][j].ans) a[ty][j].gl+=g;
else if (mx+1>a[ty][j].ans) a[ty][j].gl=g,a[ty][j].ans=mx+1;
j++;
}
i=l;
while (i<=mid) T.del(a[ty][i].v),i++;//memset会超时,这里需要清空所以del直接归0就好了
sort(a[ty]+mid+1,a[ty]+r+1,cmpid);
cdq(mid+1,r,ty);
sort(a[ty]+l,a[ty]+r+1,cmph);
}
inline void init () {
for (int i=1;i<=n;i++) ls[i]=a[0][i].v;
sort(ls+1,ls+n+1);
tot=unique(ls+1,ls+n+1)-ls-1;
for (int i=1;i<=n;i++) a[0][i].v=lower_bound(ls+1,ls+tot+1,a[0][i].v)-ls;
for (int i=1;i<=n;i++) a[1][i]=a[0][i];
}
signed main () {
#ifndef ONLINE_JUDGE
freopen("2487.in","r",stdin);
freopen("2487.out","w",stdout);
#endif
scanf("%lld",&n);
for (int i=1;i<=n;i++){
scanf("%lld%lld",&a[0][i].h,&a[0][i].v);
a[0][i].id=i;a[0][i].ans=a[0][i].gl=1;
}
init();
sort(a[0]+1,a[0]+n+1,cmpid);
cdq(1,n,0);
int ans_mx=0;db sum=0;
for (int i=1;i<=n;i++) ans_mx=max(ans_mx,a[0][i].ans);
printf("%lld\n",ans_mx);
for (int i=1;i<=n;i++) a[1][i].id=n-a[1][i].id+1,a[1][i].h*=-1,a[1][i].v=tot-a[1][i].v+1;//取反
sort(a[1]+1,a[1]+n+1,cmpid);
memset(T.mx,0,sizeof(T.mx));
memset(T.gl,0,sizeof(T.gl));
cdq(1,n,1);
for (int i=1;i<=n;i++)
if (a[0][i].ans==ans_mx) sum+=a[0][i].gl;//只有最长的终点的方案数才统计进总的方案数
sort(a[1]+1,a[1]+n+1,cmp);
sort(a[0]+1,a[0]+n+1,cmpid);
for (int i=1;i<=n;i++) {
if (a[1][i].ans+a[0][i].ans-1==ans_mx) printf("%.5lf ",a[1][i].gl*a[0][i].gl/sum);
else printf("0.00000 ");
}
return 0;
}