[SDOI2011]导弹拦截-题解

论printf("%lf",0.0)的重要性

题目地址【IN-Luogu】【IN-bzoj2244


  • 题意简述

给你nn个三维的点(x,y,z)(x,y,z),求出最长的三维不下降序列,并输出每一个点在最长的序列上的概率。


这个肯定和普通的导弹拦截不同了,普通的是没有速度的,也就是二维的,且时间一维已经有序,那么对高度求一个LISLIS(最长不下降序列)就好了。

但是现在还有速度一维,所以不能简单的做了。


那么有三个限制条件的LISLIS,显然就是一个三维偏序的问题,所以根据套路,我们对一维排序,一维CDQ m CDQ,一维数据结构就行了。

对于要求取概率,我们不能直接求出(因为不清楚是否在最优方案上),所以我们对其进行两次的求取LISLIS,从前往后求一遍最长不下降序列,再从后往前求一遍最长不上升序列,分别记为f1[i],f2[i]f_1[i],f_2[i],表示以第ii个点为结尾或者开头的最长长度,那么答案就是(减一是因为这个点ii即是开头又是结尾,所以多算了一次将其减去即可):
maxi=1n{f1[i]+f2[i]1}maxlimits_{i=1}^n{f_1[i]+f_2[i]-1}

所以判断一个点是否在最长的上面只需要判断f1[i]+f2[i]1f_1[i]+f_2[i]-1是否等于ansans即可。

那么对于方案数,我们同样在两次求取中统计一下,分别记为g1[i],g2[i]g_1[i],g_2[i],那么一个点在它最长的情况下的方案数就等于g1[i]×g2[i]g_1[i] imes g_2[i]

所以我们先统计最长情况下的总方案数:
sum=i=1ng1[i]×g2[i](要满足f1[i]+f2[i]1==ans)sum=sum_{i=1}^ng_1[i] imes g_2[i]( ext{要满足}f_1[i]+f_2[i]-1==ans)

那么对于在最长的上面的导弹被拦截的概率为g1[i]×g2[i]sumfrac{g_1[i] imes g_2[i]}{sum},不在的话就直接为00了。

这里我使用的树状数组下标为速度维护DP m DP数组,所以还需要离散化一下。

下面上代码常数略大长度略长

#include<cstdio>
#include<cstring>
#include<algorithm>
#define db double
#define lowbit(a) ((a)&(-(a)))
using namespace std;
const int M=1e5+10;
int n,ans;
int id[M];
int ls[M],tot;
int vs[M],tst;
db sum;
struct node{
	int h,v,f[2];
	db g[2];
	int id,sid;
	bool operator <(const node &a)const{return id<a.id;}
}A[M],Q[M];
bool cmp(int a,int b){return A[a].h<A[b].h||(A[a].h==A[b].h&&A[a].id<A[b].id);}
struct bit_tree{
	int f[M];db g[M];//树状数组维护最长长度与方案数 
	int del[M],top;
	void add(int a,int ff,db gg){
		for(;a<=n;a+=lowbit(a)){
			if(f[a]<ff){
				if(f[a]==0)del[++top]=a;//清理空间的小技巧
				f[a]=ff;g[a]=gg;
			}else if(f[a]==ff){
				g[a]+=gg;
			} 
		}
	}
	void query(int a,int &ff,db &gg){
		ff=0;gg=0;
		for(;a;a-=lowbit(a)){
			if(ff<f[a]){
				ff=f[a];gg=g[a];
			}else if(ff==f[a]){
				gg+=g[a];
			}
		}
	}
	void clear(){
		f[del[top]]=0,g[del[top]]=0;
		--top;
	}
}B;
void solve(int l,int r,bool type){
	if(l==r){
		if(!A[l].f[type]){
			A[l].f[type]=1;A[l].g[type]=1;//边界就是自身一个 
		}
		return;
	}
	int mid=(l+r)>>1;
	int t1=l,t2=mid+1;
	for(int i=l;i<=r;i++){//按照高度划分 
		if(A[i].sid<=mid)Q[t1++]=A[i];
		else Q[t2++]=A[i];
	}
	for(int i=l;i<=r;i++)A[i]=Q[i];
	solve(l,mid,type);//递归处理 
	t1=l;
	int ff=0;db gg=0;
	for(int i=mid+1;i<=r;i++){//计算对右边的贡献 
		for(;t1<=mid&&A[t1].id<A[i].id;++t1)
			B.add(A[t1].v,A[t1].f[type],A[t1].g[type]);
		B.query(A[i].v,ff,gg);
		if(!ff) continue;
		if(ff+1>A[i].f[type]){
			A[i].f[type]=ff+1;
			A[i].g[type]=gg;
		}else if(ff+1==A[i].f[type]){
			A[i].g[type]+=gg;
		}//更新方案和长度
	}
	while(B.top>0)B.clear();//优化清除空间 
	solve(mid+1,r,type);
	sort(A+l,A+r+1);//最后按照时间排序!!! 
}

int a,b;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&a,&b);
		A[i].h=a;A[i].v=b;
		A[i].id=i;
		ls[++tot]=A[i].h;
		vs[++tst]=A[i].v;
		id[i]=i;
	}
	sort(ls+1,ls+tot+1);
	tot=unique(ls+1,ls+tot+1)-ls-1;
	sort(vs+1,vs+tst+1);
	tst=unique(vs+1,vs+tst+1)-vs-1;
	//离散化 
	for(int i=1;i<=n;i++)
		A[i].h=lower_bound(ls+1,ls+tot+1,A[i].h)-ls,
		A[i].v=lower_bound(vs+1,vs+tst+1,A[i].v)-vs;
	for(int i=1;i<=n;i++){
		A[i].h=(tot-A[i].h+1);
		A[i].v=(tst-A[i].v+1);
	}
	sort(id+1,id+n+1,cmp);//按照高度排序 
	for(int i=1;i<=n;i++)A[id[i]].sid=i;
	solve(1,n,0);//处理顺 
	for(int i=1;i<=n;i++){
		A[i].h=(tot-A[i].h+1);
		A[i].v=(tst-A[i].v+1);
		A[i].id=(n-A[i].id+1);
		A[i].sid=(n-A[i].sid+1);
	}//翻转处理倒起 
	reverse(A+1,A+n+1);
	solve(1,n,1);
	reverse(A+1,A+n+1);
	int vv=0;
	for(int i=1;i<=n;i++){
		vv=A[i].f[0]+A[i].f[1]-1;
		if(vv>ans)ans=vv;
	}//找最大长度,减1是因为当前这个点被算了两次 
	printf("%d
",ans);
	for(int i=1;i<=n;i++){
		if(A[i].f[0]==ans)
			sum+=A[i].g[0]*A[i].g[1];
	}//求取总方案数 
	for(int i=1;i<=n;i++){
		db now=A[i].g[0]*A[i].g[1];//如果等于最大长度,概率就为它被拦截的方案数除以总的方案数,否则不等于最大长度答案就为0 
		if(A[i].f[0]+A[i].f[1]-1!=ans) printf("%lf ",0.0);//!!! printf("%lf",0)的话,它是会把0看作整数类型,所以输出会错 
		else printf("%lf ",now/sum);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/VictoryCzt/p/10053402.html