[SDOI2011]导弹拦截-题解
论printf("%lf",0.0)的重要性
题目地址【IN-Luogu】【IN-bzoj2244】
- 题意简述
给你个三维的点,求出最长的三维不下降序列,并输出每一个点在最长的序列上的概率。
这个肯定和普通的导弹拦截不同了,普通的是没有速度的,也就是二维的,且时间一维已经有序,那么对高度求一个(最长不下降序列)就好了。
但是现在还有速度一维,所以不能简单的做了。
那么有三个限制条件的,显然就是一个三维偏序的问题,所以根据套路,我们对一维排序,一维,一维数据结构就行了。
对于要求取概率,我们不能直接求出(因为不清楚是否在最优方案上),所以我们对其进行两次的求取,从前往后求一遍最长不下降序列,再从后往前求一遍最长不上升序列,分别记为,表示以第个点为结尾或者开头的最长长度,那么答案就是(减一是因为这个点即是开头又是结尾,所以多算了一次将其减去即可):
所以判断一个点是否在最长的上面只需要判断是否等于即可。
那么对于方案数,我们同样在两次求取中统计一下,分别记为,那么一个点在它最长的情况下的方案数就等于
所以我们先统计最长情况下的总方案数:
那么对于在最长的上面的导弹被拦截的概率为,不在的话就直接为了。
这里我使用的树状数组下标为速度维护数组,所以还需要离散化一下。
下面上代码常数略大长度略长
#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\n",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;
}