To_Heart—题解—— [BZOJ2244]拦截导弹
题目
题解
首先我们考虑暴力的dp,设 d p i dp_i dpi 表示以 i i i 结尾的最多的导弹拦截长度,则 d p i = max ( d p j + 1 ) , h i < h j ∧ v i < v j dp_i=\max(dp_j+1), h_i<h_j \wedge v_i<v_j dpi=max(dpj+1),hi<hj∧vi<vj,显然复杂度是 O ( n 2 ) O(n^2) O(n2) 的。考虑如何优化,发现可以用 CDQ分治。
但是CDQ分治有个问题, 在处理 l ∼ r l \sim r l∼r 的区间时,我们不能用前面区间的信息,但因为我们要比最大值,所以就不可以直接 CDQ。
那么我们考虑转换 CDQ 递归的顺序,让它中序遍历,先把左区间的答案转移到右区间,再计算右区间内部的答案,这样就可以优化 dp 了。
对于第二问,概率其实就等于选择当前导弹的方案数除以总方案数,那么我们就分别求出以这个导弹结尾和开始的最长的长度,如果这两段长度相加-1等于第一问的答案,那么这就是一种可行的方案。
所以正着倒着各跑一边 CDQ 就好了。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define db double
int n,m,N;
int f[4000005];
db f1[1000005],f2[1000005];
int dp1[1000005],dp2[100005];
struct zz{
int x,y,z,dp;
db f;
}a[4000005],b[4000005];
bool cmp1(zz x,zz y){ return x.y>y.y; }
bool cmp2(zz x,zz y){ return x.y<y.y; }
bool cmp3(zz x,zz y){ return x.x==y.x?(x.y==y.y?x.z<y.z:x.y>y.y):x.x>y.x; }
bool cmp4(zz x,zz y){ return x.x==y.x?(x.y==y.y?x.z<y.z:x.y<y.y):x.x<y.x; }
bool Check(int p,int q,int op){ return op==1? a[p].y>=a[q].y:a[p].y<=a[q].y; }
struct Bit_Tree{
ll bit1[4000005];
db bit2[4000005];
int Lowbit(int x){ return x&(-x); }
void Insert(int x,int dp,db f){ while(x<=n){ if(bit1[x]<dp)bit1[x]=dp,bit2[x]=f; else if(bit1[x]==dp)bit2[x]+=f; x+=Lowbit(x); } }
ll Find1(int x){ ll ans=0;while(x) ans=max(ans,bit1[x]),x-=Lowbit(x); return ans; }
db Find2(int x,int dp){ db ans=0;while(x){ if(bit1[x]==dp) ans+=bit2[x]; x-=Lowbit(x);}; return ans;}
void Delete(int x){ while(x<=n) bit1[x]=bit2[x]=0,x+=Lowbit(x); }
}T; //树状数组,维护dp以及方案数f
void CDQ(int l,int r,int op){
if(r<=l) return ;
int mid=(l+r)>>1;CDQ(l,mid,op);
if(op==1) sort(a+l,a+mid+1,cmp1),sort(a+mid+1,a+r+1,cmp1);
else sort(a+l,a+mid+1,cmp2),sort(a+mid+1,a+r+1,cmp2); //排序,因此可以将左边转移到右边。
int p=l,q=mid+1; //计算左边对右边的贡献
while(q<=r){
while(p<=mid&&Check(p,q,op)) T.Insert(a[p].z,a[p].dp,a[p].f),p++;
int now=T.Find1(a[q].z)+1;
if(a[q].dp<now) a[q].dp=now,a[q].f=T.Find2(a[q].z,now-1); //如果当前的答案更优,就只能更新答案,重新计算方案数。
else if(a[q].dp==now) a[q].f+=T.Find2(a[q].z,now-1); //如果说答案和dp相同,那么就直接累加方案数就好了。
q++;
}
for(int i=l;i<p;i++) T.Delete(a[i].z); //清空
if(op==1) sort(a+mid+1,a+r+1,cmp3); else sort(a+mid+1,a+r+1,cmp4);//重新排序
CDQ(mid+1,r,op);
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y),a[i].z=i,a[i].dp=1,a[i].f=1; //赋初值
sort(a+1,a+n+1,cmp3),CDQ(1,n,1);
int ans1=0;db sum=0;
for(int i=1;i<=n;i++) ans1=max(ans1,a[i].dp); printf("%lld\n",ans1);
for(int i=1;i<=n;i++) dp1[a[i].z]=a[i].dp,f1[a[i].z]=a[i].f;
for(int i=1;i<=n;i++) a[i].z=(n-a[i].z+1),a[i].dp=1,a[i].f=1;
sort(a+1,a+n+1,cmp4);CDQ(1,n,2); //正反各跑一次
for(int i=1;i<=n;i++) dp2[n-a[i].z+1]=a[i].dp,f2[n-a[i].z+1]=a[i].f;
for(int i=1;i<=n;i++) if(dp2[n-a[i].z+1]==ans1) sum+=f2[n-a[i].z+1];
for(int i=1;i<=n;i++) if(dp1[i]+dp2[i]==ans1+1) printf("%.5lf ",f1[i]*f2[i]/sum); else printf("0.00000 ");
return 0;
}