BZOJ - 2244 拦截导弹 (dp,CDQ分治+树状数组优化)

题目链接

dp进阶之CDQ分治优化dp。

前置技能:dp基本功底,CDQ分治,树状数组。

问题等价于求二维最长上升子序列,是一个三维偏序问题(时间也算一维)。

设$dp[i]=(l,x)$为以第i枚导弹结尾的最优状态,$l$代表最长上升子序列长度,$x$代表长度为l的最长上升子序列数量,则$(l_0,x_0)$比$(l_1,x_1)$更优当且仅当$l_0>l_1$或($l_0=l_1$且$x_0>x_1$)。(实际上在转移的过程中,第二个条件没什么用)

根据题意有状态转移公式:$dp[i]=\left\{\begin{matrix}\begin{aligned}&(dp[j].l+1,dp[j].x),dp[j].l>dp[i].l\\ &(dp[j].l,dp[i].x+dp[j].x),dp[j].l=dp[i].l\end{aligned}\end{matrix}\right.$,要求$p[j].i<p[i].i,p[j].x<=p[i].x,p[j].y<=p[i].y$。

这样,最长上升子序列的长度就比较容易算了。可每枚导弹被选中的概率呢?等于导弹所在的最长上升子序列的数量/最长上升子序列的总数量,这就需要计算出每个导弹所在的最长上升子序列的个数。计算方法是对导弹正反各求一次dp数组,即分别算出以每枚导弹为起点和终点的最长上升子序列的长度l和数量x,每个导弹所在的最长上升子序列的个数就是两个x的乘积(如果两个l之和为最长上升子序列长度+1的话),否则为0。最长上升子序列的总数为所有导弹所在的最长上升子序列的个数之和/最长上升子序列长度。

由于数据量是5e4的,直接转移复杂度是$O(n^2)$的显然会超时。这时CDQ分治的作用就体现了,可以将复杂度优化到$O(nlog^2n)$。

首先把所有的导弹按照时间顺序排序(输入顺序就是),保证只发生从左边向右边的状态转移。

接下来就要保证x从小到大转移了。对x排序的话显然是会破坏时间顺序的,怎么办?将序列一分为二,对左右两部分的x值分别排序,只计算左半部分向右半部分的转移就行了。这个方法可以递归进行,执行的顺序为:解决左半部分的转移->计算从左半部分向右半部分的转移->解决右半部分的转移。

还剩下一维y怎么处理?再加个树状数组就行了。由于我们只关心y值的相对大小,而不关心它的绝对大小,所以可以离散化(注意下标要从1开始,为了使树状数组能够处理)。这个树状数组的更新过程和一般的树状数组的更新过程有所不同,更新的值是一个二元组(l,x),对l要取最值,对x要累加,即要同时发挥树状数组的维护区间最值和累加功能,并且要新增一个clr函数来撤销之前的更新操作(暴力memset太浪费时间)。

具体细节见代码实现。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 typedef double db;
 5 const int N=5e4+10,inf=0x3f3f3f3f;
 6 struct P {
 7     int i,x,y;
 8     bool operator<(const P& b)const {return x<b.x;}
 9 } p[N],p2[N];
10 struct D {int l; db x;} c[N],dp[2][N];
11 void upd(D& dp,D ad) {
12     if(dp.l<ad.l)dp.l=ad.l,dp.x=ad.x;
13     else if(dp.l==ad.l)dp.x+=ad.x;
14 }
15 int n,b[N],m;
16 int lowbit(int x) {return x&-x;}
17 void add(int u,D x) {for(; u<=m; u+=lowbit(u))upd(c[u],x);}
18 D get(int u) {D ret= {0,0}; for(; u; u-=lowbit(u))upd(ret,c[u]); return ret;}
19 void clr(int u) {for(; u<=m; u+=lowbit(u))c[u]= {0,0};}
20 db ans[N];
21 
22 void CDQ(int l,int r,int f) {
23     if(l==r) {upd(dp[f][p[l].i], {1,1}); return;}
24     int mid=(l+r)>>1;
25     CDQ(l,mid,f);
26     for(int i=l; i<=r; ++i)p2[i]=p[i];
27     sort(p2+l,p2+mid+1),sort(p2+mid+1,p2+r+1);
28     int L,R;
29     for(R=mid+1,L=l; R<=r; ++R) {
30         for(; L<=mid&&p2[L].x<=p2[R].x; ++L)add(p2[L].y, {dp[f][p2[L].i].l,dp[f][p2[L].i].x});
31         D t=get(p2[R].y);
32         upd(dp[f][p2[R].i], {t.l+1,t.x});
33     }
34     for(int i=l; i<L; ++i)clr(p2[i].y);
35     CDQ(mid+1,r,f);
36 }
37 
38 int main() {
39     scanf("%d",&n);
40     for(int i=0; i<n; ++i)scanf("%d%d",&p[i].x,&p[i].y),p[i].i=i;
41     for(int i=0; i<n; ++i)b[i]=p[i].y;
42     sort(b,b+n);
43     m=unique(b,b+n)-b;
44     memset(dp,0,sizeof dp);
45     memset(c,0,sizeof c);
46     for(int i=0; i<n; ++i)p[i].x=-p[i].x,p[i].y=m-(lower_bound(b,b+m,p[i].y)-b);
47     CDQ(0,n-1,0);
48     for(int i=0; i<n; ++i)p[i].x=-p[i].x,p[i].y=m-p[i].y+1;
49     reverse(p,p+n);
50     CDQ(0,n-1,1);
51     D mx= {0,0};
52     for(int i=0; i<n; ++i)upd(mx, {dp[0][i].l+dp[1][i].l-1,dp[0][i].x*dp[1][i].x});
53     mx.x/=mx.l;
54     for(int i=0; i<n; ++i)ans[i]=dp[0][i].l+dp[1][i].l-1==mx.l?dp[0][i].x*dp[1][i].x/mx.x:0;
55     printf("%d\n",mx.l);
56     for(int i=0; i<n; ++i)printf("%f%c",ans[i]," \n"[i==n-1]);
57     return 0;
58 }

 

转载于:https://www.cnblogs.com/asdfsag/p/10547888.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值