bzoj千题计划292:bzoj2244: [SDOI2011]拦截导弹(三维偏序 CDQ分治+树状数组)
http://www.lydsy.com/JudgeOnline/problem.php?id=2244
每枚导弹成功拦截的概率 = 包含它的最长上升子序列个数/最长上升子序列总个数
pre_len [i] 表示以i结尾的最长不下降子序列的长度
pre_sum[i] 表示对应长度下的方案数
suf_len[i] 表示以i开头的最长不下降子序列长度
suf_sum[i] 表示对应长度下的方案数
若已有了这4个数组
设最长上升子序列长度=mx
那么 如果pre_len[i]+suf_len[i] - 1==mx,那么第i枚导弹在最长上升子序列上
而且在pre_sum[i]*suf_sum[i] 个最长上升子序列上
如何获取这四个数组?
这是一个三维偏序问题,CDQ分治
若i后面可以接j
第一维:i的出现时间<j的出现时间
第二维:i的高度>=j的高度
第三维:i的速度>=j的速度
具体实现:
排序第一维,CDQ过程中排序解决第二维,树状数组解决第三维
对于第三维,要离散化
树状数组查询的是<=查询位置的信息,即用它来求最长不下降子序列更方便
所以在解决以i为开头的最长不上升子序列的时候
将第二维取负,第三维i变成n-i+1
在解决以i为开始的最长不上升子序列的时候
将第一维取负
对CDQ分治更进一步的理解:
之前写CDQ分治做数据结构题的时候,
写的是先解决左边对右边的贡献,在递归两边
但是这道题,要先递归左边,再解决左边对右边的贡献,再递归右边
因为最长不下降子序列 左边更新右边的时候,左边的所有不是等价的
即也需要用左边更新之后的结果来更新右边
而像那种修改查询题,左边的修改对左边查询的影响和对右边查询的影响是等价的
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define N 50001 #define lowbit(x) ( x&-x ) int n; int h[N],v[N]; int tot,has[N]; struct node { int x,y,z; int len; double sum; }e[N]; int c[N]; double d[N]; int pre_len[N],suf_len[N]; double pre_sum[N],suf_sum[N]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } bool cmdy(node p,node q) { if(p.y==q.y) return p.z<q.z; return p.y<q.y; } bool cmdx(node p,node q) { return p.x<q.x; } void change(int pos,int len,double sum) { while(pos<=n) { if(len>c[pos]) { c[pos]=len; d[pos]=sum; } else if(len==c[pos]) d[pos]+=sum; pos+=lowbit(pos); } } int query_len(int pos) { int ans=0; while(pos) { ans= ans>=c[pos] ? ans : c[pos]; pos-=lowbit(pos); } return ans; } double query_sum(int pos,int val) { double ans=0; while(pos) { if(c[pos]==val) ans+=d[pos]; pos-=lowbit(pos); } return ans; } void clear(int pos) { while(pos<=n) { c[pos]=d[pos]=0; pos+=lowbit(pos); } } void cdq(int l,int r) { if(l==r) return; int mid=l+r>>1; cdq(l,mid); sort(e+l,e+mid+1,cmdy); sort(e+mid+1,e+r+1,cmdy); int i=l,j=mid+1; int len; double sum; for(;j<=r;++j) { while(i<=mid && e[i].y<=e[j].y) { change(e[i].z,e[i].len,e[i].sum); i++; } len=query_len(e[j].z)+1; sum=query_sum(e[j].z,len-1); if(len>e[j].len) { e[j].len=len; e[j].sum=sum; } else if(len==e[j].len) e[j].sum+=sum; } for(int k=l;k<=mid;++k) clear(e[k].z); sort(e+mid+1,e+r+1,cmdx); cdq(mid+1,r); } void work() { sort(has+1,has+n+1); tot=unique(has+1,has+n+1)-has-1; for(int i=1;i<=n;++i) v[i]=lower_bound(has+1,has+tot+1,v[i])-has; for(int i=1;i<=n;++i) { e[i].x=i; e[i].y=-h[i]; e[i].z=tot-v[i]+1; e[i].len=e[i].sum=1; } cdq(1,n); for(int i=1;i<=n;++i) pre_len[e[i].x]=e[i].len,pre_sum[e[i].x]=e[i].sum; for(int i=1;i<=n;++i) { e[i].x=-i; e[i].y=h[i]; e[i].z=v[i]; e[i].len=e[i].sum=1; } sort(e+1,e+n+1,cmdx); cdq(1,n); for(int i=1;i<=n;++i) suf_len[-e[i].x]=e[i].len,suf_sum[-e[i].x]=e[i].sum; } void get_ans() { int mx=0; for(int i=1;i<=n;++i) mx=max(mx,pre_len[i]); printf("%d\n",mx); double cnt=0; for(int i=1;i<=n;++i) if(pre_len[i]==mx) cnt+=pre_sum[i]; for(int i=1;i<=n;++i) if(pre_len[i]+suf_len[i]-1==mx) printf("%.5lf ",(double)pre_sum[i]*suf_sum[i]/cnt); else printf("0 "); } int main() { read(n); for(int i=1;i<=n;++i) { read(h[i]); read(v[i]); has[i]=v[i]; } work(); get_ans(); }