「SDOI2011」拦截导弹 CDQ分治+树状数组
题意:
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。
我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。
输入:
第一行包含一个正整数\(n\),表示敌军导弹数量;
下面\(n\)行按顺序给出了敌军所有导弹信息:
第\(i+1\)行包含\(2\)个正整数\(h_i\) 和\(v_i\) ,分别表示第\(i\)枚导弹的高度和速度。
输出:
输出包含两行。
第一行为一个正整数,表示最多能拦截掉的导弹数量;
第二行包含\(n\)个\(0\)到\(1\)之间的实数,第\(i\)个数字表示第ii枚导弹被拦截掉的概率(你可以保留任意多位有效数字)。
思路:
关于第一问,可以考虑如果\(dp[j]\)可以被\(dp[i]\)更新,需要满足的条件有:
1、\(i<j\)
2、\(h_i>=h_j\)
3、\(v_i>=v_j\)
存在三个限制条件,也就是三维偏序问题,可以直接用CDQ进行维护,在CDQ中按\(id\)分成两个区间,按照 \(v\)进行排序,再用树状数组维护\(h\)
接下来就可以考虑第二问了。
每个导弹被拦截的概率=这个导弹出现的方案数/总的方案数,这里的方案指的是能达到拦截导弹最多的方案。
这里用两个数组进行记录,\(sum[i]\) 到表示\(dp[i]\)有多少种转移方法,\(sz[i]\)表示由\(i\)向后转移有多少种达到最大方案的转移数。
如果\(n\)比较小,可以直接考虑\(n^2\)计算。
for(int i=n; i>=1; i--) {
if(dp[i]==ans)cnt[i]=1;
if(dp[i]==1)sum[i]=1;
}
for(int j=1; j<=n; j++)
for(int i=j+1; i<=n; i++) {
if(dp[i]==dp[j]+1&&a[j].h>=a[i].h&&a[j].v>=a[i].v)sum[i]+=sum[j];
}
for(int i=1; i<=n; i++)if(dp[i]==ans)tot+=sum[i];//用来计算总的方案数
for(int i=n; i>=1; i--)
for(int j=i-1; j>=1; j--)
if(dp[i]==dp[j]+1&&a[j].h>=a[i].h&&a[j].v>=a[i].v)cnt[j]+=cnt[i];
但是\(n\)比较大的时候显然就不行了,这个时候可以看到还是存在三个限制条件,因此可以继续用CDQ进行操作。
对于\(sum[i]\)可以直接在第一次CDQ中处理(因为都是正序),只用在树状数组中多记一个个数,而对于\(sz\)CDQ每次先处理的就变成了右区间。往回更新的时候,还有一个条件是\(dp[j]==dp[i]+1\)所以可以将树状数组中记录的最大值变成这个点到最大值还需要几个数,那么每次更新的时候只用再判一下\(dp[i]+mx\)是否等于最大值。
注意:总的方案数会扎long long 所以可以开double
#include<bits/stdc++.h>
#define M 50005
#define db double
#define lowbit(x) (x&-x)
using namespace std;
bool cur1;
int n,b[M],c[M],id1,id2,dp[M],ans,Dp[M];
db sum[M],sz[M];
struct node {
int h,v,id;
bool operator<(const node&_)const {
if(v!=_.v)return v>_.v;
return h>_.h;
}
} a[M],Q[M],C[M];
struct Tree {
int cnt[M];
db Sum[M];
void Init() {
for(int i=1; i<=id1; i++)Sum[i]=cnt[i]=0;
}
void add(int x,int y,db v) {
while(x) {
if(cnt[x]<y)cnt[x]=y,Sum[x]=v;
else if(cnt[x]==y)Sum[x]+=v;
x-=lowbit(x);
}
}
void sum(int x,int &res,db &y) {
res=y=0;
while(x<=id1) {
if(res<cnt[x])res=cnt[x],y=Sum[x];
else if(res==cnt[x])y+=Sum[x];
x+=lowbit(x);
}
}
void clear(int x) {
while(x)cnt[x]=0,Sum[x]=0,x-=lowbit(x);
}
void INit() {
for(int i=1; i<=id1; i++)Sum[i]=cnt[i]=0;
}
void Add(int x,int y,db v) {
if(x==0||y==0)return;
while(x<=id1) {
if(cnt[x]<y)cnt[x]=y,Sum[x]=v;
else if(cnt[x]==y)Sum[x]+=v;
x+=lowbit(x);
}
}
void get(int x,int &res,db &y) {
res=0,y=0;
while(x) {
if(res<cnt[x])res=cnt[x],y=Sum[x];
else if(res==cnt[x])y+=Sum[x];
x-=lowbit(x);
}
}
void Clear(int x) {
while(x<=id1)cnt[x]=Sum[x]=0,x+=lowbit(x);
}
} T;
void CDQ(int l,int r) {
if(l>r)return;
if(l==r) {
dp[l]=max(dp[l],1);
if(dp[l]==1)sum[l]=1;
ans=max(ans,dp[l]);
return;
}
int mid=(l+r)>>1;
CDQ(l,mid);
for(int i=l; i<=r; i++)Q[i]=a[i],Q[i].id=i;
sort(Q+l,Q+mid+1),sort(Q+mid+1,Q+r+1);
int x=l,y=mid+1,now=l;
while(x<=mid&&y<=r) {
if(Q[x].v>=Q[y].v)C[now++]=Q[x++];
else C[now++]=Q[y++];
}
while(x<=mid)C[now++]=Q[x++];
while(y<=r)C[now++]=Q[y++];
for(int i=l; i<=r; i++) {
if(C[i].id<=mid)T.add(C[i].h,dp[C[i].id],sum[C[i].id]);
else {
int res;
db tot;
T.sum(C[i].h,res,tot);
res++;
if(res>dp[C[i].id])dp[C[i].id]=res,sum[C[i].id]=tot;
else if(res==dp[C[i].id])sum[C[i].id]+=tot;
}
}
for(int i=l; i<=r; i++)if(C[i].id<=mid)T.clear(C[i].h);
CDQ(mid+1,r);
}
void cdq(int l,int r) {
if(l>r)return;
if(l==r) {
if(dp[l]==ans)sz[l]=1;
Dp[l]=max(Dp[l],1);
return;
}
int mid=(l+r)>>1;
cdq(mid+1,r);//先处理右区间
for(int i=l; i<=r; i++)Q[i]=a[i],Q[i].id=i;
sort(Q+l,Q+mid+1),sort(Q+mid+1,Q+r+1);
int x=l,y=mid+1,now=l;
while(x<=mid&&y<=r) {
if(Q[x].v>=Q[y].v)C[now++]=Q[x++];
else C[now++]=Q[y++];
}
while(x<=mid)C[now++]=Q[x++];
while(y<=r)C[now++]=Q[y++];
for(int i=r; i>=l; i--) {
if(C[i].id>mid)T.Add(C[i].h,ans-dp[C[i].id]+1,sz[C[i].id]);
else {
int res;
db tot;
T.get(C[i].h,res,tot);
if(res+dp[C[i].id]==ans)Dp[C[i].id]=res+1,sz[C[i].id]+=tot;
}
}
for(int i=l; i<=r; i++)if(C[i].id>mid)T.Clear(C[i].h);
cdq(l,mid);
}
struct P2 {
void solve() {
ans=0;
T.Init(),CDQ(1,n);
printf("%d\n",ans);
}
} p2;
struct P3 {
void solve() {
T.INit();
db tot=0;
cdq(1,n);
for(int i=1; i<=n; i++)if(dp[i]==ans)tot+=sum[i];//计算总的方案
for(int i=1; i<=n; i++) {
printf("%.5lf",1.0*sz[i]*sum[i]/tot);
if(i!=n)printf(" ");
else printf("\n");
}
}
} p3;
bool cur2;
int main() {
// printf("%lf\n",(&cur2-&cur1)/1024.0/1024);
// freopen("2.in","r",stdin);
freopen("missile.in","r",stdin);
freopen("missile.out","w",stdout);
scanf("%d",&n);
for(int i=1; i<=n; i++)scanf("%d%d",&a[i].h,&a[i].v),b[++id1]=a[i].h,c[++id2]=a[i].v;
sort(b+1,b+id1),sort(c+1,c+id2);
id1=unique(b+1,b+id1)-b-1;
id2=unique(c+1,c+id2)-c-1;
for(int i=1; i<=n; i++) {
a[i].h=lower_bound(b+1,b+id1,a[i].h)-b;
a[i].v=lower_bound(c+1,c+id2,a[i].v)-c;
}
p2.solve();
p3.solve();
return 0;
}