To_Heart—题解—— [BZOJ2244]拦截导弹

题目

Link.

题解

首先我们考虑暴力的dp,设 \(dp_i\) 表示以 \(i\) 结尾的最多的导弹拦截长度,则 \(dp_i=\max(dp_j+1), h_i<h_j \wedge v_i<v_j\),显然复杂度是 \(O(n^2)\) 的。考虑如何优化,发现可以用 CDQ分治。

但是CDQ分治有个问题, 在处理 \(l \sim 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;
}

posted @ 2022-02-16 20:54  To_Heart  阅读(32)  评论(0编辑  收藏  举报