CDQ分治优化一维DP转移——[SDOI2011]拦截导弹

在这之前P1020 导弹拦截

Description [SDOI2011]拦截导弹

给出\(n\)个导弹,每个导弹三个属性\(t_i,h_i,v_i\),分别为时间,发射高度,速度。对于已经拦截的导弹 \(i\),下一个能被拦截的导弹\(j\)必须满足\(i<j,h_i\geq h_j,v_i\geq v_j\),请求出最多能拦截的导弹数量。对此,可能有多种方案,请再求出每个导弹被拦截的概率。

Solution

显然这是一个DP的题目。根据题意,可以得到DP方程\(dp_i=1+max(dp[j],\forall i>j,h_j\geq h_i,v_j\geq v_i)\),对于这个方程普通的DP需要时间\(O(n^2)\),需要优化,我们注意到后面三个条件类似于三维偏序,所以我们考虑

CDP分治优化一维DP转移

先考虑CDQ是什么。(我想只有我不知道),一种广泛的用法是处理点对有关问题,详情OI-Wiki-part1

对于普通的CDQ,处理区间\((l,r)\)顺序是这样的:

1.处理区间 \((l,mid)\)

2.处理区间\((mid+1,r)\)

3.处理\(l\leq i\leq mid,mid+1\leq j \leq r\)的关系

但是对于CDQ优化DP转移,我们需要顺序:1、3、2。

为什么呢?我们考虑DP顺序,对于正在更新的位置\(i\),我们需要将所有位置在它前面的值都更新完毕,所以需要先更新整个区间再更新其他的区间。

解决了优化DP的问题,我们可以求一个最长不升子序列就能得到第一问的答案。对于第二问,我们考虑记录总的方案数,用经过一个点的方案数除以总方案数\(sum\)就是答案。

考虑对于一个点\(i\),如果它在目标序列中却不是目标序列的终点,我们需要知道在它后面还有多少方案数能够到达目标序列,那么就可以将整个序列取反(就是大的变小小的变大)再求一遍最长不升子序列就能知道在它后面的方案数\(a[1][i].gl\),那么\((a[0][i].gl*a[1][i].gl)/sum\)就可以得到这个点出现的概率了。

Code(有注释在代码里)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define db double//不开double会炸qwq
#define int long long
using namespace std;
const int N=5e4+5;
struct node {
	int h,v,ans,id;//ans表示以点i为结尾的最长不升子序列长度,gl为方案数
	db gl;
}a[2][N];//a[0]是正着的,a[1]是反着的
int n,ls[N],tot;

struct Tree1{
	int mx[N];
	db gl[N];

	inline void add (int x,int maxx,db g) {
		while (x) {
			if (maxx==mx[x])	gl[x]+=g;
			else if(maxx>mx[x]) mx[x]=maxx,gl[x]=g;
			x-=x&(-x);
		}
	}

	inline void query (int x,int &mxx,db &g) {
		while (x<=tot) {
			if (mxx==mx[x]) g+=gl[x];
			else	if (mxx<mx[x])	mxx=mx[x],g=gl[x];
			x+=x&(-x);
		}
	}

	inline void del (int x) {
		while (x) {
			mx[x]=gl[x]=0;
			x-=x&(-x);
		}
	}
}T;//树状数组求后缀

inline bool cmpid (node x,node y) {return x.id<y.id;}
inline bool cmph (node x,node y) {return x.h>y.h;}
inline bool cmp (node x,node y)	{return x.id>y.id;}

inline void cdq (int l,int r,int ty) {
	if (l==r)	return;
	int mid=(l+r)>>1;
	cdq(l,mid,ty);
	sort(a[ty]+mid+1,a[ty]+r+1,cmph);
	int i=l,j=mid+1;
	while (j<=r) {
		while (a[ty][i].h>=a[ty][j].h&&i<=mid) {
			T.add(a[ty][i].v,a[ty][i].ans,a[ty][i].gl);
			i++;
		}
		int mx=0;db g=0;
		T.query(a[ty][j].v,mx,g);
		if (mx+1==a[ty][j].ans)	a[ty][j].gl+=g;
		else if (mx+1>a[ty][j].ans)	a[ty][j].gl=g,a[ty][j].ans=mx+1;
		j++;
	}
	i=l;
	while (i<=mid)	T.del(a[ty][i].v),i++;//memset会超时,这里需要清空所以del直接归0就好了
	sort(a[ty]+mid+1,a[ty]+r+1,cmpid);
	cdq(mid+1,r,ty);
	sort(a[ty]+l,a[ty]+r+1,cmph);
}

inline void init () {
	for (int i=1;i<=n;i++)	ls[i]=a[0][i].v;
	sort(ls+1,ls+n+1);
	tot=unique(ls+1,ls+n+1)-ls-1;
	for (int i=1;i<=n;i++)	a[0][i].v=lower_bound(ls+1,ls+tot+1,a[0][i].v)-ls;
	for (int i=1;i<=n;i++)	a[1][i]=a[0][i];
}

signed main () {
#ifndef ONLINE_JUDGE
	freopen("2487.in","r",stdin);
	freopen("2487.out","w",stdout);
#endif
	scanf("%lld",&n);
	for (int i=1;i<=n;i++){
		scanf("%lld%lld",&a[0][i].h,&a[0][i].v);
		a[0][i].id=i;a[0][i].ans=a[0][i].gl=1;
	}
	init();
	sort(a[0]+1,a[0]+n+1,cmpid);
	cdq(1,n,0);
	int ans_mx=0;db sum=0;
	for (int i=1;i<=n;i++)	ans_mx=max(ans_mx,a[0][i].ans);
	printf("%lld\n",ans_mx);
	for (int i=1;i<=n;i++)	a[1][i].id=n-a[1][i].id+1,a[1][i].h*=-1,a[1][i].v=tot-a[1][i].v+1;//取反
	sort(a[1]+1,a[1]+n+1,cmpid);
	memset(T.mx,0,sizeof(T.mx));
	memset(T.gl,0,sizeof(T.gl));
	cdq(1,n,1);
	for (int i=1;i<=n;i++) 
		if (a[0][i].ans==ans_mx)	sum+=a[0][i].gl;//只有最长的终点的方案数才统计进总的方案数
	sort(a[1]+1,a[1]+n+1,cmp);
	sort(a[0]+1,a[0]+n+1,cmpid);
	for (int i=1;i<=n;i++) {
		if (a[1][i].ans+a[0][i].ans-1==ans_mx)	printf("%.5lf ",a[1][i].gl*a[0][i].gl/sum);
		else printf("0.00000 ");
	}
	return 0;
}
posted @ 2020-10-20 22:19  蒟蒻zyx_qwq  阅读(144)  评论(1编辑  收藏  举报