题目大意
给定若干元素,每个元素有 \(3\) 个属性 \(t_i,h_i,v_i\),求出一个使得对于 \(\forall i,j,i>j\),\(t_i>t_j,h_i\le h_j,v_i\le v_j\) 均成立的最长的子序列 \(a\) 的长度。并计算每个元素在所有的可能的 \(a\) 方案中的出现概率。
思路分析
- 先看第一个问题:
按 \(t\) 排好序后容易发现其实就是一个二维的不上升子序列问题。
首先有一个 \(n^2\) 的 DP 方程:
\[f_i=\max(f_j)+1,j<i,h_j\ge h_i,v_j\ge v_i \]其中 \(f_i\) 表示以 \(i\) 结尾的最长不上升子序列的长度。
但问题类似于三维偏序的形式,因此考虑用 CDQ 分治进行优化。
考虑 CDQ 分治的过程,发现只需要在归并时查询前缀最值即可。
具体的说。在 CDQ 分治时,先递归左半部分,再将左右部分分别按照 \(h\) 排序,维护左右双指针 \(j,i\),若 \(h_i\le h_j\),那么在树状数组的 \(v_j\) 位置加入 \(f_j\) 的贡献,否则通过树状数组中 \(v_i\) 的前缀最值更新 \(f_i\),最后清空树状数组,复原数组,递归右半部分。
- 再看第二个问题:
我们发现,一个元素的出现概率可以通过计数的方法计算。
具体的说,我们在维护 \(f_i\) 时顺便维护 \(g_i\),表示长度为 \(f_i\) 以 \(i\) 结尾的的不上升子序列的个数。
但这样还是不行,我们发现一个元素可能位于整个序列的最长不上升子序列的中间位置。因此,我们可以令 \(f'_i\) 表示以 \(i\) 开始的最长不上升子序列的长度,令 \(g'_i\) 表示长度为 \(f'_i\) 以 \(i\) 开始的的不上升子序列的个数。
那么这时就可以统计答案了,当 \(f_i+f'_i-1=\text{len}\) 时(\(\text{len}\) 表示整个序列的最长不上升子序列的长度),即这个元素可以组成整个序列的最长不上升子序列时,它出现的概率为:
\[\frac{g_ig'_i}{\text{sum}} \]其中 \(\text{sum}\) 表示整个序列的最长不上升子序列的个数。
而 \(f'\) 和 \(g'\) 可以通过将数组逆序取反后再跑一遍一模一样的 CDQ 解决。
- 其他
需要对 \(v\) 离散化。
记得开 double
。
代码
代码小卡了一下常,目前是最优解。
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=50100;
int n,m,in1,in2;
int Y[N],c[N];
#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<16,stdin),S==T)?EOF:*S++)
char B[1<<16],*S=B,*T=B;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||'9'<ch) ch=getchar();
while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}//整数快读
inline void dwrite(int x){
if(!x){putchar('0');return ;}
int bit[20],p=0,i;
for (;x;x/=10) bit[++p]=x%10;
for (i=p;i>0;--i) putchar(bit[i]+48);
}//整数快写
inline void write(double x){
static int n=100000,k=5;
int y=(int)(x*n)%n;x=(int)x;
dwrite(x),putchar('.');
int bit[8],p=0,i;
for(;p<k;y/=10) bit[++p]=y%10;
for(i=p;i>0;i--) putchar(bit[i]+48);
}//小数快写
struct Node{
int x,y,id;
}a[N],b[N];
struct Data{
int maxn;
double cnt;
Data operator + (const Data &b){
Data c;
c.maxn=max(maxn,b.maxn);
c.cnt=((maxn==c.maxn)?cnt:0)+((b.maxn==c.maxn)?b.cnt:0);
return c;//f 和 g 的结构题,以重载运算符的形式更新
}
}f[N],f2[N],ans;
struct Tree_Array{
Data a[N];
int tt[N],times;//树状数组时间戳 O(1) 清空
#define lowbit(x) ((x)&(-(x)))
inline void add(int x,Data v){
for(;x;x-=lowbit(x)){
if(tt[x]==times) a[x]=a[x]+v;
else a[x]=v,tt[x]=times;
}
}
inline void clear(){times++;}
inline Data ask(int x){
Data ans=Data{0,0};
for(;x<=m;x+=lowbit(x))
if(tt[x]==times) ans=ans+a[x];
return ans;
}
}tree;
void CDQ(int l,int r){
if(l==r){f[a[l].id]=f[a[l].id]+Data{1,1};return ;}//边界
int mid=l+r>>1,j=l;
CDQ(l,mid);
sort(a+l,a+mid+1,[](Node a,Node b){return a.x>b.x;});
sort(a+mid+1,a+r+1,[](Node a,Node b){return a.x>b.x;});//按 x 排序
for(register int i=mid+1;i<=r;++i){
while(j<=mid&&a[j].x>=a[i].x) tree.add(a[j].y,f[a[j].id]),j++;//加入树状数组
Data temp=tree.ask(a[i].y);temp.maxn++;//统计答案
f[a[i].id]=f[a[i].id]+temp;
}
tree.clear();
int minn=n+1,maxn=0;//手写的桶排,按照 id 来排
for(int i=mid+1;i<=r;++i){
c[a[i].id]=i;
minn=min(minn,a[i].id);
maxn=max(maxn,a[i].id);
}
for(int i=minn;i<=maxn;++i)
b[i]=a[c[i]];
for(int i=mid+1;i<=r;++i)
a[i]=b[i];
CDQ(mid+1,r);
}
int main(){
n=read();
for(register int i=1;i<=n;++i){
in1=read();in2=read();
a[i]=Node{in1,in2,i};
Y[i]=in2;
}
sort(Y+1,Y+n+1);
m=unique(Y+1,Y+n+1)-Y-1;
for(register int i=1;i<=n;++i)
a[i].y=lower_bound(Y+1,Y+m+1,a[i].y)-Y;//离散化
CDQ(1,n);
for(register int i=1;i<=n;++i) ans=ans+f[i];//更新答案,只需要一边
dwrite(ans.maxn);putchar('\n');
for(register int i=1;i<=n;++i){//数组反转
f2[i]=f[i];
f[i]=Data{0,0};
a[i].id=n-a[i].id+1;
a[i].x=-a[i].x;
a[i].y=m-a[i].y+1;
}
for(int i=1;i<=n;++i)//一样的桶排
c[a[i].id]=i;
for(int i=1;i<=n;++i)
b[i]=a[c[i]];
for(int i=1;i<=n;++i)
a[i]=b[i];
CDQ(1,n);
reverse(f+1,f+n+1);
for(register int i=1;i<=n;++i){
if(f[i].maxn+f2[i].maxn-1==ans.maxn)
write(1.0*f[i].cnt*f2[i].cnt/ans.cnt),putchar(' ');
else putchar('0'),putchar(' ');//判定答案
}
return 0;
}
标签:Node,拦截导弹,int,题解,序列,maxn,CDQ,P2487,Data
From: https://www.cnblogs.com/TKXZ133/p/17457559.html