P2487 [SDOI2011]拦截导弹
题目
普及组题目
这道题让我重新认识了CDQ计算顺序的问题
第一问求的就是二维的最长不上升子序列。设
把DP的过程转成离线,那么就是一个三维偏序问题。记三元组
那么最多能拦截的导弹数
至于第二问呢?
先设
首先:一个导弹被拦截的概率
至于计算
注意
思路其实是挺简单的,重点是以下部分:
在经过调代码
下面将以我调代码的心路历程来展现这个问题:
注意:我一开始的写法是用归并排序对
- 第一阶段:计算顺序
一开始学CDQ的时候,看到的都是这样的:
递归计算左半部分
计算左半部分对右半部分的贡献
递归计算右边部分
我相信很多人学CDQ看的第一道题是归并排序计算逆序对吧?而我们在写归并排序的时候,都是先排序好
然而在这道题中这是错的,递归到
所以我们调整计算方法,先递归到
- 数组问题
这个问题与个人的写法有关吧
可能只有我那么傻(⊙_⊙)?
按照上面的,计算左边对右边的贡献是类似归并排序一样的过程,是用两个指针在两个部分扫,把较大的那一个放到数组里(不妨设为
当递归到右半部分
那怎么解决这个问题呢?既然
写得有点乱,看着代码会好一点。。。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=5e4+10;
int n,tot;
int tmp[N];
struct ask
{
int t;//位置
int x;//高度
int y;//速度
}num[N],v[N],rt[N];//num是一开始输入和倒序的数组,v是CDQ中一层层排序的数组,rt是用来排序右半部分的临时数组
struct node
{
int maxn;
double cnt;
node operator + (const node &a) const//重载加号,方便计算
{
if(maxn>a.maxn) return *this;
if(maxn<a.maxn) return a;
return (node){maxn,cnt+a.cnt};
}
}f[N][2];
struct tree
{
int left,right;
node val;
}t[4*N];
inline bool cmp1(ask a,ask b)//按照位置排序
{
return a.t<b.t;
}
inline bool cmp2(ask a,ask b)//按照高度排序
{
return a.x>b.x;
}
inline void build(int p,int l,int r)//线段树建树
{
t[p].left=l,t[p].right=r;
t[p].val=(node){0,0};
if(l==r) return;
int mid=(t[p].left+t[p].right)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
inline void change(int p,int pos,node c)//单点修改
{
if(t[p].left==t[p].right)
{
t[p].val=t[p].val+c;
return;
}
int mid=(t[p].left+t[p].right)/2;
if(pos<=mid) change(p*2,pos,c);
else change(p*2+1,pos,c);
t[p].val=t[p*2].val+t[p*2+1].val;
}
inline void clear(int p,int pos)//把修改的清空
{
t[p].val=(node){0,0};
if(t[p].left==t[p].right) return;
int mid=(t[p].left+t[p].right)/2;
if(pos<=mid) clear(p*2,pos);
else clear(p*2+1,pos);
}
inline node query(int p,int l,int r)//区间查询
{
if(l<=t[p].left&&r>=t[p].right) return t[p].val;
int mid=(t[p].left+t[p].right)/2;
node ans=(node){0,0};
if(l<=mid) ans=ans+query(p*2,l,r);
if(r>mid) ans=ans+query(p*2+1,l,r);
return ans;
}
inline void CDQ(int l,int r,int stg)
{
if(l==r){ if(f[v[l].t][stg].maxn==0) f[v[l].t][stg]=(node){1,1}; return; }//注意这里不能用+号,而是判断是否为0
int mid=(l+r)/2,p=l,q=mid+1;
CDQ(l,mid,stg);//递归计算左半部分
for(int i=mid+1;i<=r;i++)//把右半部分的塞进临时数组
rt[i]=v[i];
sort(rt+mid+1,rt+r+1,cmp2);//对右半部分直接排序
for(int i=l;i<=r;i++)
{
if((p<=mid&&v[p].x>=rt[q].x)||q>r) change(1,v[p].y,f[v[p].t][stg]),p++;
else
{
node cmp=query(1,rt[q].y,tot);
if(cmp.maxn) cmp.maxn++,f[rt[q].t][stg]=f[rt[q].t][stg]+cmp;//不为0时才更新答案,注意要+1
q++;
}
}
for(int i=l;i<=mid;i++)
clear(1,v[i].y);//把修改的清空
CDQ(mid+1,r,stg);//递归计算右半部分
sort(v+l,v+r+1,cmp2);//对当前区间[l,r]排序
}
inline void solve(int stg)
{
for(int i=1;i<=n;i++)
v[i]=num[i],tmp[i]=num[i].y;
sort(v+1,v+n+1,cmp1);
sort(tmp+1,tmp+n+1);
tot=unique(tmp+1,tmp+n+1)-(tmp+1);
for(int i=1;i<=n;i++)
v[i].y=lower_bound(tmp+1,tmp+tot+1,v[i].y)-tmp;//离散化
build(1,1,tot);
CDQ(1,n,stg);
}
int main()
{
int ans=0;
double sum=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
num[i].t=i;
scanf("%d%d",&num[i].x,&num[i].y);
}
solve(0);
for(int i=1;i<=n;i++)
num[i]=(ask){n-i+1,-num[i].x,-num[i].y};//把数组倒过来,高度和速度取负
solve(1);
for(int i=1;i<=n;i++)
ans=max(ans,f[i][0].maxn);//计算最大长度
printf("%d\n",ans);
for(int i=1;i<=n;i++)
if(f[i][0].maxn==ans) sum+=f[i][0].cnt;//计算总方案数
for(int i=1;i<=n;i++)
{
if(f[i][0].maxn+f[n-i+1][1].maxn==ans+1) printf("%.5lf ",(f[i][0].cnt*f[n-i+1][1].cnt)/sum);//计算每一个导弹被拦截的概率
else printf("0.00000 ");
}
return 0;
}