bzoj 2244 [SDOI2011]拦截导弹

本文介绍了一个经典的计算机科学竞赛题目——SDOI2011拦截导弹问题,详细解析了如何使用CDQ分治算法解决三维偏序问题,并通过实例代码展示了具体的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2244: [SDOI2011]拦截导弹

Time Limit: 30 Sec   Memory Limit: 512 MBSec   Special Judge
Submit: 662   Solved: 267
[ Submit][ Status][ Discuss]

Description

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。

我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。

Input

第一行包含一个正整数n,表示敌军导弹数量;

下面 行按顺序给出了敌军所有导弹信息:

i+1行包含2个正整数hivi,分别表示第 枚导弹的高度和速度。

Output

输出包含两行。

第一行为一个正整数,表示最多能拦截掉的导弹数量;

第二行包含n01之间的实数,第i个数字表示第i枚导弹被拦截掉的概率(你可以保留任意多位有效数字)。

Sample Input

4

3 30

4 40

6 60

3 30

Sample Output

2

0.33333 0.33333 0.33333 1.00000

【数据规模和约定】


对于100%的数据,1≤n≤5*104, 1≤hi ,vi≤109;

均匀分布着约30%的数据,所有vi均相等。

均匀分布着约50%的数据,满足1≤hi ,vi≤1000。

HINT

鸣谢kac提供sj程序!

Source

[ Submit][ Status][ Discuss]




【分析】

比较难搞的CDQ分治...因为个人智障等原因从早上10:30写到下午16:20...卧槽6个小时(虽然中间午休了一会)

曾经的导弹拦截只用求最长上升子序列,相当于二维偏序,本题加了一维,所以三维偏序,我们要用CDQ降一维。

把整个序列翻转后,考虑二维偏序的最长上升子序列如何求出。CDQ分治中三维分别为id,x,y,id为导弹的先后顺序,为方便我们将x,y都离散化掉。用a[i].f表示以导弹i为结尾的LIS长度,a[i].g表示以导弹i为结尾在LIS的条件下总共有几种方案。我们在处理区间(l,r)时,要先以id排序,处理区间(l,mid),处理完之后将(l,mid)和(mid+1,r)以x为关键字排序,设置两个指针,用树状数组更新答案,注意这里的树状数组有一些特殊的地方,详见代码。


那么导弹i为结尾的LIS都处理出来了,还要处理导弹i为开头的。事实上很简单,只要把序列翻转回来,x=n+1-x,y=n+1-y,就可以调用之前的函数统计答案了。

第二问的答案显然是一颗导弹所在的lis数/总的lis数。用g数组求解即可。


【代码】

//bzoj 2244
#include<bits/stdc++.h>
#define ll long long
#define M(a) memset(a,0,sizeof a)
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int mxn=50005;
int n,m,t1,t2,T,kd;
int h[mxn],s[mxn],tim[mxn],t_f[mxn];
double t_g[mxn],ans[mxn];
struct bullet
{
	int id,x,y,f[2];double g[2];
}a[mxn],tmp[mxn];
inline bool comp(bullet u,bullet v) 
{
	if(u.x==v.x) return u.y<v.y;
	return u.x<v.x;
}
inline bool cmp(bullet u,bullet v) 
{
	return u.id<v.id;
}
inline int lowbit(int x) {return x&-x;}
inline void add(int u,int f,double g)
{
	for(int i=u;i<=n;i+=lowbit(i))
	{
	    if(tim[i]!=T) tim[i]=T,t_f[i]=t_g[i]=0;
	    if(f>t_f[i]) t_f[i]=f,t_g[i]=g;
 	    else if(f==t_f[i]) t_g[i]+=g;
	}
}
inline void getsum(int u,int &len,double &calc)
{
	len=0,calc=0.0;
	for(int i=u;i>=1;i-=lowbit(i))
	  if(tim[i]==T)
	  {
	  	  if(t_f[i]>len) len=t_f[i],calc=t_g[i];
	  	  else if(t_f[i]==len) calc+=t_g[i];
	  }
}
inline void CDQ(int l,int r)
{
//	if(l==5 && r==7 && kd==1)
//	{
//		puts("fuck");
//	}
	if(l==r)
	{
		if(!a[l].f[kd]) a[l].f[kd]=a[l].g[kd]=1;
		return;
	}
	int i,j,mid=l+r>>1,l1=l,l2=mid+1;
	sort(a+l,a+r+1,cmp);
	CDQ(l,mid);T++;
	sort(a+mid+1,a+r+1,comp);
	for(l2=mid+1;l2<=r;l2++)
	{
		for(l1;l1<=mid&&a[l1].x<=a[l2].x;l1++)
		  add(a[l1].y,a[l1].f[kd],a[l1].g[kd]);
		int mx;double sum;
		getsum(a[l2].y,mx,sum);
		if(mx>0)
		{
			if(mx+1>a[l2].f[kd])
			  a[l2].f[kd]=mx+1,a[l2].g[kd]=sum;
			else if(mx+1==a[l2].f[kd])
			  a[l2].g[kd]+=sum;
		}
	}
	CDQ(mid+1,r);
	sort(a+l,a+r+1,comp);
//	fo(i,1,n) printf("test=%d\n",a[i].id);
//	puts("");
}
int main()
{
	int i,j;
	scanf("%d",&n);
	for(i=n;i>=1;i--)
	{
		scanf("%d%d",&a[i].x,&a[i].y);
		h[i]=a[i].x,s[i]=a[i].y,a[i].id=i;
	}
	sort(h+1,h+n+1),sort(s+1,s+n+1);
	t1=unique(h+1,h+n+1)-h-1;
	t2=unique(s+1,s+n+1)-s-1;
	fo(i,1,n) a[i].x=lower_bound(h+1,h+t1+1,a[i].x)-h;
	fo(i,1,n) a[i].y=lower_bound(s+1,s+t2+1,a[i].y)-s;
	kd=0,CDQ(1,n);
	
	sort(a+1,a+n+1,cmp);
	reverse(a+1,a+n+1);
	fo(i,1,n) a[i].x=n-a[i].x+1;
	fo(i,1,n) a[i].y=n-a[i].y+1;
	fo(i,1,n) a[i].id=i;
	kd=1,CDQ(1,n);
	//part one.
	int mxlen=0;
	double sum=0.0;
	fo(i,1,n)
	  if(a[i].f[0]>mxlen)
	    mxlen=a[i].f[0],sum=a[i].g[0];
	  else if(a[i].f[0]==mxlen)
	    sum+=a[i].g[0];
//	printf("test=%.5lf\n",sum);
	printf("%d\n",mxlen);
	
	fo(i,1,n) if(a[i].f[0]+a[i].f[1]-1==mxlen)
	  ans[a[i].id]=(a[i].g[0]*a[i].g[1])/sum;
	fo(i,1,n-1) printf("%.5lf ",ans[i]);
	printf("%.5lf\n",ans[n]);
	return 0;
}
/*
7
2 5
3 6
2 5
2 4
3 5
1 9
6 3
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值